OAuth2TokenEndpointFilter是什么啊?
它是 Spring Authorization Server(授权服务器)中,专门负责处理 /oauth2/token 端点请求的核心过滤器。 授权服务器签发token说的就是它的功能。
说人话就是:
拦截发送到 OAuth 2.0 令牌端点(通常是 /oauth2/token)的 HTTP 请求,请求经过认证之后,给客户端返回token
下图展示了过滤器的核心过滤流程和其依赖的组件。
从上图我们可以清晰的看到,OAuth2TokenEndpointFilter 依赖如下组件:
- tokenEndpointMatcher
- authenticationConverter
- authenticationManager
- authenticationSuccessHandler
- authenticationFailureHandler
上面这些组件都是支持开发者进行custom定制的,可以通OAuth2TokenEndpointConfigurer进行配置的。我们暂不去讨论定制化,就讨论框架默认情况下所做的处理。
tokenEndpointMatcher
this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
if (!this.tokenEndpointMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
用来统一请求入口,表明当前过滤器只处理/oauth2/token 的请求,其他请求一律放行。
authenticationConverter
将原始的 HTTP 请求(特别是发送到 /oauth2/token 端点的请求)转换成一个特定类型的 Authentication 对象以便后续的 AuthenticationManager 能够处理。
在 Spring Security 的认证流程中,核心是 AuthenticationManager,它接收一个 Authentication 类型的对象,对其进行认证,并返回一个已认证的 Authentication 对象。
比如下面的请求:
curl --location --request POST 'http://localhost:9000/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: JSESSIONID=4150EED52BD5B1AE1FA0BC913D697258' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=oidc-client' \
--data-urlencode 'client_secret=secret'
AuthenticationManager 并不能直接处理这种原始的 HTTP 请求。它需要一个结构化的、类型安全的“认证请求对象”。
这时,authenticationConverter 就登场了!
它在流程中的位置
-
OAuth2TokenEndpointFilter拦截请求:这个 Filter 拦截了所有发往/oauth2/token的请求。 -
调用
converter:Filter 不会自己解析请求,而是调用你配置的authenticationConverter。 -
converter解析请求并生成Authentication对象:- 它检查请求中的
grant_type参数。 - 根据
grant_type的值(如authorization_code,refresh_token,client_credentials等),选择合适的子converter。 - 例如,如果
grant_type=authorization_code,则OAuth2AuthorizationCodeAuthenticationConverter会被调用。 - 这个子
converter会从请求中提取code,redirect_uri,client_id,client_secret等参数,并将它们封装成一个OAuth2AuthorizationCodeAuthenticationToken对象。
- 它检查请求中的
-
将
Authentication对象交给AuthenticationManager:OAuth2TokenEndpointFilter拿到这个Authentication对象后,将其提交给AuthenticationManager进行真正的认证和令牌颁发。
委托模式
-
DelegatingAuthenticationConverter:这是一个“委托式”转换器。它内部持有一个List<AuthenticationConverter>。 -
工作方式:当它收到一个
HttpServletRequest时,它会遍历内部的每一个子converter,调用它们的convert()方法。 -
哪个子
converter能处理,就用哪个:每个子converter都会检查这个请求是否符合它能处理的类型(通常是通过grant_type判断)。例如:OAuth2AuthorizationCodeAuthenticationConverter只处理grant_type=authorization_code的请求。OAuth2RefreshTokenAuthenticationConverter只处理grant_type=refresh_token的请求。
-
返回第一个成功转换的结果:一旦某个子
converter成功将请求转换为对应的Authentication对象(如OAuth2AuthorizationCodeAuthenticationToken),DelegatingAuthenticationConverter就返回这个对象,不再尝试后面的converter。
authenticationManager
通过配置一个能够识别和处理多种 Authentication 类型的 AuthenticationManager,通常是 ProviderManager,它内部包含多个 AuthenticationProvider,每个 Provider 负责处理一种特定的授权类型
核心机制:AuthenticationManager 与 AuthenticationProvider
在 Spring Security 中,AuthenticationManager 的核心实现是 ProviderManager。它的职责是:
- 接收一个
Authentication对象(如OAuth2AuthorizationCodeAuthenticationToken)。 - 遍历其内部注册的所有
AuthenticationProvider。 - 调用每个
provider.supports(authentication.getClass())方法来检查该provider是否支持这种Authentication类型。 - 如果支持,则调用
provider.authenticate(authentication)进行认证。 - 返回第一个成功认证的结果。
我们用client-credentials授权模式进行举例子:
OAuth2ClientCredentialsAuthenticationConverter 将原始请求转化成OAuth2ClientCredentialsAuthenticationToken。
OAuth2ClientCredentialsAuthenticationProvider 支持认证 OAuth2ClientCredentialsAuthenticationToken
认证之后返回的是 OAuth2AccessTokenAuthenticationToken 里面封装了accessToken。里面的细节我们可以单独写一篇文章讨论,因为涉及到了token的定制化。
authenticationFailureHandler and authenticationSuccessHandler
认证有两种结果,肯定是认证成功和认证失败两种结果,分别由对应的成功和失败处理器来决定给用户的响应
private AuthenticationSuccessHandler authenticationSuccessHandler = new OAuth2AccessTokenResponseAuthenticationSuccessHandler();
private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler();
从源码上看,框架提供了默认的成功和失败处理器。
成功处理器: 负责将生成的令牌信息格式化并写回 HTTP 响应。
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
。。。。
OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
.tokenType(accessToken.getTokenType())
.scopes(accessToken.getScopes());
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
}
if (refreshToken != null) {
builder.refreshToken(refreshToken.getTokenValue());
}
if (!CollectionUtils.isEmpty(additionalParameters)) {
builder.additionalParameters(additionalParameters);
}
if (this.accessTokenResponseCustomizer != null) {
// @formatter:off
OAuth2AccessTokenAuthenticationContext accessTokenAuthenticationContext =
OAuth2AccessTokenAuthenticationContext.with(accessTokenAuthentication)
.accessTokenResponse(builder)
.build();
// @formatter:on
this.accessTokenResponseCustomizer.accept(accessTokenAuthenticationContext);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Customized access token response");
}
}
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
this.accessTokenResponseConverter.write(accessTokenResponse, null, httpResponse);
}
上面的代码摘选自默认的成功处理器,通过构建者模式构建一个OAuth2AccessTokenResponse,最后通过accessTokenResponseConverter写入到http响应中。
总结
里面其实有很多细节我没有细说
比如token的定制化
自定义authenticationProvider如何落地。
上面的流程是因为我最近在梳理项目,发现以前读不懂的代码突然可以理解了,把那一瞬间的想法想记录下来。
最后我自己挖的坑 我也得自己埋,预告下 将要写单独的文章,实现token的定制,以及自定义认证逻辑。