OAuth 2.0 Bearer Tokens
Bearer Token Resolution
默认情况下,资源服务器在 Authorization
头中寻找承载令牌。然而,这可以用几种方式自定义。
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
例如,你或许有需要从自定义头中读取承载令牌。要实现这一点,你可以公开一个 DefaultBearerTokenResolver
作为 bean,或者将一个实例连接到 DSL 中,正如你在以下示例中看到的那样:
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>
或者,在提供者同时使用自定义头和值的情况下,你可以使用 HeaderBearerTokenResolver
。
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
或者,您可能希望从表单参数中读取令牌,您可以通过将配置为 DefaultBearerTokenResolver
来实现,如下所示:
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
现在既然你的资源服务器已经验证了令牌,那么将它传递给下游服务可能会很方便。这使用 {security-api-url}org/springframework/security/oauth2/server/resource/web/reactive/function/client/ServletBearerExchangeFilterFunction.html[ServletBearerExchangeFilterFunction]
非常简单,你可以在以下示例中看到:
Now that your 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
-
Kotlin
@Bean
public WebClient rest() {
return WebClient.builder()
.filter(new ServletBearerExchangeFilterFunction())
.build();
}
@Bean
fun rest(): WebClient {
return WebClient.builder()
.filter(ServletBearerExchangeFilterFunction())
.build()
}
当使用上述 WebClient
来执行请求时,Spring Security 将查找当前的 Authentication
并提取任何 {security-api-url}org/springframework/security/oauth2/core/AbstractOAuth2Token.html[AbstractOAuth2Token]
凭证。然后,它将在 Authorization
标头中传播该令牌。
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
-
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()
将调用 https://other-service.example.com/endpoint
,为您添加 bearer token Authorization
头。
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
-
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()
在这种情况下,过滤器将回退并简单地将请求转发到其余的 Web 过滤器链。
In this case, the filter will fall back and simply forward the request onto the rest of the web filter chain.
与 {security-api-url}org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.html[OAuth 2.0客户端过滤器函数] 不同,此过滤器函数不会尝试在令牌过期时对其进行续订。要获得此级别的支持,请使用 OAuth 2.0 客户端过滤器。 |
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
目前 RestTemplate
没有 ServletBearerExchangeFilterFunction
的等效项,但您可以用您自己的拦截器简单地传播请求的 bearer token:
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
}
与 {security-api-url}org/springframework/security/oauth2/client/OAuth2AuthorizedClientManager.html[OAuth 2.0 授权客户端管理器] 不同,此过滤器拦截器不会尝试在令牌过期时对其进行续订。要获得此级别的支持,请使用 OAuth 2.0 Authorized Client Manager 创建一个拦截器。 |
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 OAuth 2.0 Authorized Client Manager. |
Bearer Token Failure
bearer token 可能因多种原因而无效。例如,令牌可能不再处于活动状态。
A bearer token may be invalid for a number of reasons. For example, the token may no longer be active.
在此情况下,资源服务器会抛出一个 InvalidBearerTokenException
。像其他异常一样,这会导致 OAuth 2.0 Bearer Token 错误响应:
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"
此外,它会发布为 AuthenticationFailureBadCredentialsEvent
,您可以像这样 listen for in your application:
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
}
}
}