-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Separate Resource Server Servlet Docs
Issue gh-10367
- Loading branch information
Showing
9 changed files
with
3,072 additions
and
3,063 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
= OAuth2 | ||
:page-section-summary-toc: 1 | ||
|
||
Spring Security provides comprehensive OAuth 2 support. | ||
This section discusses how to integrate OAuth 2 into your servlet based application. | ||
|
||
* xref:servlet/oauth2/oauth2-login.adoc[] | ||
* xref:servlet/oauth2/oauth2-client.adoc[] | ||
* xref:servlet/oauth2/oauth2-resourceserver.adoc[] | ||
|
3,058 changes: 0 additions & 3,058 deletions
3,058
docs/modules/ROOT/pages/servlet/oauth2/oauth2-resourceserver.adoc
This file was deleted.
Oops, something went wrong.
290 changes: 290 additions & 0 deletions
290
docs/modules/ROOT/pages/servlet/oauth2/resource-server/bearer-tokens.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
= OAuth 2.0 Bearer Tokens | ||
|
||
[[oauth2resourceserver-bearertoken-resolver]] | ||
== Bearer Token Resolution | ||
|
||
By default, Resource Server looks for a bearer token in the `Authorization` header. | ||
This, however, can be customized in a handful of ways. | ||
|
||
=== Reading the Bearer Token from a Custom Header | ||
|
||
For example, you may have a need to read the bearer token from a custom header. | ||
To achieve this, you can expose a `DefaultBearerTokenResolver` as a bean, or wire an instance into the DSL, as you can see in the following example: | ||
|
||
.Custom Bearer Token Header | ||
==== | ||
.Java | ||
[source,java,role="primary"] | ||
---- | ||
@Bean | ||
BearerTokenResolver bearerTokenResolver() { | ||
DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); | ||
bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION); | ||
return bearerTokenResolver; | ||
} | ||
---- | ||
.Kotlin | ||
[source,kotlin,role="secondary"] | ||
---- | ||
@Bean | ||
fun bearerTokenResolver(): BearerTokenResolver { | ||
val bearerTokenResolver = DefaultBearerTokenResolver() | ||
bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION) | ||
return bearerTokenResolver | ||
} | ||
---- | ||
.Xml | ||
[source,xml,role="secondary"] | ||
---- | ||
<http> | ||
<oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver"/> | ||
</http> | ||
<bean id="bearerTokenResolver" | ||
class="org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver"> | ||
<property name="bearerTokenHeaderName" value="Proxy-Authorization"/> | ||
</bean> | ||
---- | ||
==== | ||
|
||
Or, in circumstances where a provider is using both a custom header and value, you can use `HeaderBearerTokenResolver` instead. | ||
|
||
=== Reading the Bearer Token from a Form Parameter | ||
|
||
Or, you may wish to read the token from a form parameter, which you can do by configuring the `DefaultBearerTokenResolver`, as you can see below: | ||
|
||
.Form Parameter Bearer Token | ||
==== | ||
.Java | ||
[source,java,role="primary"] | ||
---- | ||
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); | ||
resolver.setAllowFormEncodedBodyParameter(true); | ||
http | ||
.oauth2ResourceServer(oauth2 -> oauth2 | ||
.bearerTokenResolver(resolver) | ||
); | ||
---- | ||
.Kotlin | ||
[source,kotlin,role="secondary"] | ||
---- | ||
val resolver = DefaultBearerTokenResolver() | ||
resolver.setAllowFormEncodedBodyParameter(true) | ||
http { | ||
oauth2ResourceServer { | ||
bearerTokenResolver = resolver | ||
} | ||
} | ||
---- | ||
.Xml | ||
[source,xml,role="secondary"] | ||
---- | ||
<http> | ||
<oauth2-resource-server bearer-token-resolver-ref="bearerTokenResolver"/> | ||
</http> | ||
<bean id="bearerTokenResolver" | ||
class="org.springframework.security.oauth2.server.resource.web.HeaderBearerTokenResolver"> | ||
<property name="allowFormEncodedBodyParameter" value="true"/> | ||
</bean> | ||
---- | ||
==== | ||
|
||
== Bearer Token Propagation | ||
|
||
Now that you're resource server has validated the token, it might be handy to pass it to downstream services. | ||
This is quite simple with `{security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServletBearerExchangeFilterFunction.html[ServletBearerExchangeFilterFunction]`, which you can see in the following example: | ||
|
||
==== | ||
.Java | ||
[source,java,role="primary"] | ||
---- | ||
@Bean | ||
public WebClient rest() { | ||
return WebClient.builder() | ||
.filter(new ServletBearerExchangeFilterFunction()) | ||
.build(); | ||
} | ||
---- | ||
.Kotlin | ||
[source,kotlin,role="secondary"] | ||
---- | ||
@Bean | ||
fun rest(): WebClient { | ||
return WebClient.builder() | ||
.filter(ServletBearerExchangeFilterFunction()) | ||
.build() | ||
} | ||
---- | ||
==== | ||
|
||
When the above `WebClient` is used to perform requests, Spring Security will look up the current `Authentication` and extract any `{security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]` credential. | ||
Then, it will propagate that token in the `Authorization` header. | ||
|
||
For example: | ||
|
||
==== | ||
.Java | ||
[source,java,role="primary"] | ||
---- | ||
this.rest.get() | ||
.uri("https://other-service.example.com/endpoint") | ||
.retrieve() | ||
.bodyToMono(String.class) | ||
.block() | ||
---- | ||
.Kotlin | ||
[source,kotlin,role="secondary"] | ||
---- | ||
this.rest.get() | ||
.uri("https://other-service.example.com/endpoint") | ||
.retrieve() | ||
.bodyToMono<String>() | ||
.block() | ||
---- | ||
==== | ||
|
||
Will invoke the `https://other-service.example.com/endpoint`, adding the bearer token `Authorization` header for you. | ||
|
||
In places where you need to override this behavior, it's a simple matter of supplying the header yourself, like so: | ||
|
||
==== | ||
.Java | ||
[source,java,role="primary"] | ||
---- | ||
this.rest.get() | ||
.uri("https://other-service.example.com/endpoint") | ||
.headers(headers -> headers.setBearerAuth(overridingToken)) | ||
.retrieve() | ||
.bodyToMono(String.class) | ||
.block() | ||
---- | ||
.Kotlin | ||
[source,kotlin,role="secondary"] | ||
---- | ||
this.rest.get() | ||
.uri("https://other-service.example.com/endpoint") | ||
.headers{ headers -> headers.setBearerAuth(overridingToken)} | ||
.retrieve() | ||
.bodyToMono<String>() | ||
.block() | ||
---- | ||
==== | ||
|
||
In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain. | ||
|
||
[NOTE] | ||
Unlike the {security-api-url}org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.html[OAuth 2.0 Client filter function], this filter function makes no attempt to renew the token, should it be expired. | ||
To obtain this level of support, please use the OAuth 2.0 Client filter. | ||
|
||
=== `RestTemplate` support | ||
|
||
There is no `RestTemplate` equivalent for `ServletBearerExchangeFilterFunction` at the moment, but you can propagate the request's bearer token quite simply with your own interceptor: | ||
|
||
==== | ||
.Java | ||
[source,java,role="primary"] | ||
---- | ||
@Bean | ||
RestTemplate rest() { | ||
RestTemplate rest = new RestTemplate(); | ||
rest.getInterceptors().add((request, body, execution) -> { | ||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||
if (authentication == null) { | ||
return execution.execute(request, body); | ||
} | ||
if (!(authentication.getCredentials() instanceof AbstractOAuth2Token)) { | ||
return execution.execute(request, body); | ||
} | ||
AbstractOAuth2Token token = (AbstractOAuth2Token) authentication.getCredentials(); | ||
request.getHeaders().setBearerAuth(token.getTokenValue()); | ||
return execution.execute(request, body); | ||
}); | ||
return rest; | ||
} | ||
---- | ||
.Kotlin | ||
[source,kotlin,role="secondary"] | ||
---- | ||
@Bean | ||
fun rest(): RestTemplate { | ||
val rest = RestTemplate() | ||
rest.interceptors.add(ClientHttpRequestInterceptor { request, body, execution -> | ||
val authentication: Authentication? = SecurityContextHolder.getContext().authentication | ||
if (authentication != null) { | ||
execution.execute(request, body) | ||
} | ||
if (authentication!!.credentials !is AbstractOAuth2Token) { | ||
execution.execute(request, body) | ||
} | ||
val token: AbstractOAuth2Token = authentication.credentials as AbstractOAuth2Token | ||
request.headers.setBearerAuth(token.tokenValue) | ||
execution.execute(request, body) | ||
}) | ||
return rest | ||
} | ||
---- | ||
==== | ||
|
||
|
||
[NOTE] | ||
Unlike the {security-api-url}org/springframework/security/oauth2/client/OAuth2AuthorizedClientManager.html[OAuth 2.0 Authorized Client Manager], this filter interceptor makes no attempt to renew the token, should it be expired. | ||
To obtain this level of support, please create an interceptor using the xref:servlet/oauth2/oauth2-client.adoc#oauth2client[OAuth 2.0 Authorized Client Manager]. | ||
|
||
[[oauth2resourceserver-bearertoken-failure]] | ||
== Bearer Token Failure | ||
|
||
A bearer token may be invalid for a number of reasons. For example, the token may no longer be active. | ||
|
||
In these circumstances, Resource Server throws an `InvalidBearerTokenException`. | ||
Like other exceptions, this results in an OAuth 2.0 Bearer Token error response: | ||
|
||
[source,http request] | ||
---- | ||
HTTP/1.1 401 Unauthorized | ||
WWW-Authenticate: Bearer error_code="invalid_token", error_description="Unsupported algorithm of none", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1" | ||
---- | ||
|
||
Additionally, it is published as an `AuthenticationFailureBadCredentialsEvent`, which you can xref:servlet/authentication/events.adoc#servlet-events[listen for in your application] like so: | ||
|
||
==== | ||
.Java | ||
[source,java,role="primary"] | ||
---- | ||
@Component | ||
public class FailureEvents { | ||
@EventListener | ||
public void onFailure(AuthenticationFailureBadCredentialsEvent badCredentials) { | ||
if (badCredentials.getAuthentication() instanceof BearerTokenAuthenticationToken) { | ||
// ... handle | ||
} | ||
} | ||
} | ||
---- | ||
.Kotlin | ||
[source,kotlin,role="secondary"] | ||
---- | ||
@Component | ||
class FailureEvents { | ||
@EventListener | ||
fun onFailure(badCredentials: AuthenticationFailureBadCredentialsEvent) { | ||
if (badCredentials.authentication is BearerTokenAuthenticationToken) { | ||
// ... handle | ||
} | ||
} | ||
} | ||
---- | ||
==== |
58 changes: 58 additions & 0 deletions
58
docs/modules/ROOT/pages/servlet/oauth2/resource-server/index.adoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
[[oauth2resourceserver]] | ||
= OAuth 2.0 Resource Server | ||
:figures: servlet/oauth2 | ||
|
||
Spring Security supports protecting endpoints using two forms of OAuth 2.0 https://tools.ietf.org/html/rfc6750.html[Bearer Tokens]: | ||
|
||
* https://tools.ietf.org/html/rfc7519[JWT] | ||
* Opaque Tokens | ||
|
||
This is handy in circumstances where an application has delegated its authority management to an https://tools.ietf.org/html/rfc6749[authorization server] (for example, Okta or Ping Identity). | ||
This authorization server can be consulted by resource servers to authorize requests. | ||
|
||
This section provides details on how Spring Security provides support for OAuth 2.0 https://tools.ietf.org/html/rfc6750.html[Bearer Tokens]. | ||
|
||
[NOTE] | ||
==== | ||
Working samples for both {gh-samples-url}/servlet/spring-boot/java/oauth2/resource-server/jwe[JWTs] and {gh-samples-url}/servlet/spring-boot/java/oauth2/resource-server/opaque[Opaque Tokens] are available in the {gh-samples-url}[Spring Security Samples repository]. | ||
==== | ||
|
||
Let's take a look at how Bearer Token Authentication works within Spring Security. | ||
First, we see that, like xref:servlet/authentication/passwords/basic.adoc#servlet-authentication-basic[Basic Authentication], the https://tools.ietf.org/html/rfc7235#section-4.1[WWW-Authenticate] header is sent back to an unauthenticated client. | ||
|
||
.Sending WWW-Authenticate Header | ||
image::{figures}/bearerauthenticationentrypoint.png[] | ||
|
||
The figure above builds off our xref:servlet/architecture.adoc#servlet-securityfilterchain[`SecurityFilterChain`] diagram. | ||
|
||
image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the resource `/private` for which it is not authorized. | ||
|
||
image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`. | ||
|
||
image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__. | ||
The configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`] is an instance of {security-api-url}org/springframework/security/oauth2/server/resource/web/BearerTokenAuthenticationEntryPoint.html[`BearerTokenAuthenticationEntryPoint`] which sends a WWW-Authenticate header. | ||
The `RequestCache` is typically a `NullRequestCache` that does not save the request since the client is capable of replaying the requests it originally requested. | ||
|
||
When a client receives the `WWW-Authenticate: Bearer` header, it knows it should retry with a bearer token. | ||
Below is the flow for the bearer token being processed. | ||
|
||
[[oauth2resourceserver-authentication-bearertokenauthenticationfilter]] | ||
.Authenticating Bearer Token | ||
image::{figures}/bearertokenauthenticationfilter.png[] | ||
|
||
The figure builds off our xref:servlet/architecture.adoc#servlet-securityfilterchain[`SecurityFilterChain`] diagram. | ||
|
||
image:{icondir}/number_1.png[] When the user submits their bearer token, the `BearerTokenAuthenticationFilter` creates a `BearerTokenAuthenticationToken` which is a type of xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] by extracting the token from the `HttpServletRequest`. | ||
|
||
image:{icondir}/number_2.png[] Next, the `HttpServletRequest` is passed to the `AuthenticationManagerResolver`, which selects the `AuthenticationManager`. The `BearerTokenAuthenticationToken` is passed into the `AuthenticationManager` to be authenticated. | ||
The details of what `AuthenticationManager` looks like depends on whether you're configured for xref:servlet/oauth2/resource-server/jwt.adoc#oauth2resourceserver-jwt-minimalconfiguration[JWT] or xref:servlet/oauth2/resource-server/opaque-token.adoc#oauth2resourceserver-opaque-minimalconfiguration[opaque token]. | ||
|
||
image:{icondir}/number_3.png[] If authentication fails, then __Failure__ | ||
|
||
* The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder] is cleared out. | ||
* The `AuthenticationEntryPoint` is invoked to trigger the WWW-Authenticate header to be sent again. | ||
|
||
image:{icondir}/number_4.png[] If authentication is successful, then __Success__. | ||
|
||
* The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder]. | ||
* The `BearerTokenAuthenticationFilter` invokes `FilterChain.doFilter(request,response)` to continue with the rest of the application logic. |
Oops, something went wrong.