You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
8.6 KiB
312 lines
8.6 KiB
= 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 |
|
[tabs] |
|
====== |
|
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 |
|
[tabs] |
|
====== |
|
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 your resource server has validated the token, it might be handy to pass it to downstream services. |
|
This is quite simple with javadoc:org.springframework.security.oauth2.server.resource.web.reactive.function.client.ServletBearerExchangeFilterFunction[], which you can see in the following example: |
|
|
|
[tabs] |
|
====== |
|
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 javadoc:org.springframework.security.oauth2.core.AbstractOAuth2Token[] credential. |
|
Then, it will propagate that token in the `Authorization` header. |
|
|
|
For example: |
|
|
|
[tabs] |
|
====== |
|
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: |
|
|
|
[tabs] |
|
====== |
|
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 javadoc:org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction[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: |
|
|
|
[tabs] |
|
====== |
|
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) { |
|
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 |
|
} |
|
---- |
|
====== |
|
|
|
|
|
[NOTE] |
|
Unlike the javadoc:org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager[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/client/index.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: |
|
|
|
[tabs] |
|
====== |
|
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 |
|
} |
|
} |
|
} |
|
---- |
|
======
|
|
|