This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.3.4! |
OAuth 2.0 Bearer Tokens
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:
-
Java
-
Kotlin
-
Xml
@Bean
BearerTokenResolver bearerTokenResolver() {
DefaultBearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION);
return bearerTokenResolver;
}
@Bean
fun bearerTokenResolver(): BearerTokenResolver {
val bearerTokenResolver = DefaultBearerTokenResolver()
bearerTokenResolver.setBearerTokenHeaderName(HttpHeaders.PROXY_AUTHORIZATION)
return bearerTokenResolver
}
<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:
-
Java
-
Kotlin
-
Xml
DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver();
resolver.setAllowFormEncodedBodyParameter(true);
http
.oauth2ResourceServer(oauth2 -> oauth2
.bearerTokenResolver(resolver)
);
val resolver = DefaultBearerTokenResolver()
resolver.setAllowFormEncodedBodyParameter(true)
http {
oauth2ResourceServer {
bearerTokenResolver = resolver
}
}
<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 your resource server has validated the token, it might be handy to pass it to downstream services.
This is quite simple with ServletBearerExchangeFilterFunction
, which you can see in the following example:
-
Java
-
Kotlin
@Bean
public WebClient rest() {
return WebClient.builder()
.filter(new ServletBearerExchangeFilterFunction())
.build();
}
@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 AbstractOAuth2Token
credential.
Then, it will propagate that token in the Authorization
header.
For example:
-
Java
-
Kotlin
this.rest.get()
.uri("https://other-service.example.com/endpoint")
.retrieve()
.bodyToMono(String.class)
.block()
this.rest.get()
.uri("https://other-service.example.com/endpoint")
.retrieve()
.bodyToMono<String>()
.block()
Will invoke the 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
-
Kotlin
this.rest.get()
.uri("https://other-service.example.com/endpoint")
.headers(headers -> headers.setBearerAuth(overridingToken))
.retrieve()
.bodyToMono(String.class)
.block()
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.
Unlike the 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
-
Kotlin
@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;
}
@Bean
fun rest(): RestTemplate {
val rest = RestTemplate()
rest.interceptors.add(ClientHttpRequestInterceptor { request, body, execution ->
val authentication: Authentication? = SecurityContextHolder.getContext().authentication
if (authentication == null) {
return execution.execute(request, body)
}
if (authentication.credentials !is AbstractOAuth2Token) {
return execution.execute(request, body)
}
request.headers.setBearerAuth(authentication.credentials.tokenValue)
execution.execute(request, body)
})
return rest
}
Unlike the 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 OAuth 2.0 Authorized Client Manager. |
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:
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 listen for in your application like so:
-
Java
-
Kotlin
@Component
public class FailureEvents {
@EventListener
public void onFailure(AuthenticationFailureBadCredentialsEvent badCredentials) {
if (badCredentials.getAuthentication() instanceof BearerTokenAuthenticationToken) {
// ... handle
}
}
}
@Component
class FailureEvents {
@EventListener
fun onFailure(badCredentials: AuthenticationFailureBadCredentialsEvent) {
if (badCredentials.authentication is BearerTokenAuthenticationToken) {
// ... handle
}
}
}