目录
示例
大体流程
配置器
OAuth2AuthorizationServerConfigurer
createConfigurers
configure
过滤器
OAuth2TokenEndpointFilter
自定义配置
配置示例
tokenEndpoint
自定义请求参数转换器
CustomDelegatingAuthenticationConverter
PasswordGrantAuthenticationConverter
PasswordRefreshTokenAuthenticationConverter
自定义认证提供者
PasswordGrantAuthenticationProvider
PasswordRefreshTokenAuthenticationProvider
时序图
本篇文章我们来研究spring-security5框架如何实现OAuth2的密码授权模式,即用户向客户端提供自己的用户名和密码,客户端使用这些信息向“服务提供商”索要授权。
先从一个简单示例开始,如下图所示:
示例
首先,新建一个config包用于存放spring-security通用配置;
然后,新建一个AuthSecurityConfig类,给AuthSecurityConfig类中加上@EnableWebSecurity 注解后,这样便会自动被 Spring发现并注册。
@Configuration @EnableWebSecurity public class AuthSecurityConfig{ @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<?> tokenGenerator) throws Exception { OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>(); authorizationServerConfigurer.tokenEndpoint(tokenEndpoint -> tokenEndpoint .accessTokenRequestConverter(new CustomDelegatingAuthenticationConverter()) .authenticationProvider(new PasswordGrantAuthenticationProvider()) .authenticationProvider(new PasswordRefreshTokenAuthenticationProvider())); RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); http.requestMatcher(endpointsMatcher) .apply(authorizationServerConfigurer); return http.build(); } |
在这里,首先实例化一个配置器OAuth2AuthorizationServerConfigurer对象;
然后,自定义accessTokenRequestConverter和authenticationProvider配置信息;
最后,将该配置器对象应用到HttpSecurity 对象即可。
大体流程
点击示例里的OAuth2AuthorizationServerConfigurer类,如下所示:
配置器
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> { private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers(); |
在这里,调用createConfigurers()方法,创建各种相关的子配置器对象。
点击createConfigurers()方法,如下所示:
private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() { Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>(); configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess)); configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess)); configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess)); configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess)); configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess)); configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess)); return configurers; } |
在这里,我们看到程序创建了配置器OAuth2TokenEndpointConfigurer对象。配置器对象创建好了之后,重点要关注该对象的configure()方法。
点击对象的configure()方法,如下所示:
public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configurer { ... ... @Override <B extends HttpSecurityBuilder<B>> void configure(B builder) { AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); OAuth2TokenEndpointFilter tokenEndpointFilter = new OAuth2TokenEndpointFilter( authenticationManager, providerSettings.getTokenEndpoint()); if (this.accessTokenRequestConverter != null) { tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter); } ... ... builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class); } ... } |
在这里,我们看到程序创建了过滤器OAuth2TokenEndpointFilter对象,并且传入请求参数转换器对象accessTokenRequestConverter和认证管理器对象authenticationManager。
过滤器对象如下所示:
过滤器
OAuth2TokenEndpointFilter
public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter { ... ... @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { if (!this.tokenEndpointMatcher.matches(request)) { filterChain.doFilter(request, response); return; } try { ... ... Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request); ... ... OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication); this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication); } catch (OAuth2AuthenticationException ex) { ... ... } } private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { ... ... } } |
在这里,我们看到整个过滤逻辑主要包括请求参数转换操作convert(request)和认证操作authenticate(authorizationGrantAuthentication)两个。
请求参数转换操作由authenticationConverter对象来处理,该对象在实例化过滤器时传入。
认证操作由authenticationManager对象处理,该对象在实例化过滤器时传入。
具体实现逻辑见后续章节。
自定义配置
配置示例
@Configuration @EnableWebSecurity public class AuthSecurityConfig{ @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<?> tokenGenerator) throws Exception { OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer<>(); authorizationServerConfigurer.tokenEndpoint(tokenEndpoint -> tokenEndpoint .accessTokenRequestConverter(new CustomDelegatingAuthenticationConverter()) .authenticationProvider(new PasswordGrantAuthenticationProvider()) .authenticationProvider(new PasswordRefreshTokenAuthenticationProvider())); RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); http.requestMatcher(endpointsMatcher) .apply(authorizationServerConfigurer); return http.build(); } |
在这里,通过调用tokenEndpoint()方法实现对请求参数转换器和认证提供者的自定义。传入的匿名内部类,采用链式调用的方式先自定义请求参数转换器CustomDelegatingAuthenticationConverter,再自定义认证提供者PasswordGrantAuthenticationProvider和PasswordRefreshTokenAuthenticationProvider。
tokenEndpoint
public OAuth2AuthorizationServerConfigurer<B> tokenEndpoint(Customizer<OAuth2TokenEndpointConfigurer> tokenEndpoint(Customizer) { tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class)); return this; } |
在这里,通过调用getConfigurer()方法获取配置器实例OAuth2TokenEndpointConfigurer,然后将该实例传入给匿名内部类的接口方法。
自定义请求参数转换器
CustomDelegatingAuthenticationConverter
这是一个转换器的代表类,代表如下两个转换器类:PasswordGrantAuthenticationConverter、PasswordRefreshTokenAuthenticationConverter。
@Slf4j public class CustomDelegatingAuthenticationConverter implements AuthenticationConverter { private final List<AuthenticationConverter> converters; public CustomDelegatingAuthenticationConverter() { this.converters = Arrays.asList( new PasswordGrantAuthenticationConverter(), new PasswordRefreshTokenAuthenticationConverter()); } @Nullable @Override public Authentication convert(HttpServletRequest request) { for (AuthenticationConverter converter : this.converters) { Authentication authentication = converter.convert(request); if (authentication != null) { return authentication; } } log.error("没有匹配到合适的Converter"); throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode()); } } |
在这里,按顺序调用每个转换器实例的convert()方法,只要调用结果返回不为null则表示调用成功,成功则结束convert操作;如果遍历所有的转换器都没有调用成功,则抛出异常。
PasswordGrantAuthenticationConverter
public class PasswordGrantAuthenticationConverter implements AuthenticationConverter { public PasswordGrantAuthenticationConverter() { } @Override public Authentication convert(HttpServletRequest request) { // 判断登录授权模式是否是支持的类型 String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); String username = request.getParameter(OAuth2ParameterNames.USERNAME); if (!”password”.equals(grantType) || StringUtils.isEmpty(username)) { return null; } ... ... Authentication clientPrincipal=securityContextHolder.getContext().getAuthentication(); // 获取用户名与密码,并校验密码的合法性 String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); // 获取用户信息 CustomUserDetails userDetails = customUserDetailsService.loadUser(username, password); // 返回自定义的PasswordGrantAuthenticationToken对象 return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters, , userDetails); } } |
在这里,将用户输入的username 和password 转换为Authentication对象。
PasswordRefreshTokenAuthenticationConverter
public class PasswordRefreshTokenAuthenticationConverter implements AuthenticationConverter { @Override public Authentication convert(HttpServletRequest request) { String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) { return null; } Authentication clientPrincipal=SecurityContextHolder.getContext().getAuthentication(); String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN); String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); Set<String> requestedScopes = Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); ... ... return new OAuth2RefreshTokenAuthenticationToken( refreshToken, clientPrincipal, requestedScopes, additionalParameters); } } |
在这里,将用户输入的refreshToken 转换为Authentication对象。
自定义认证提供者
PasswordGrantAuthenticationProvider
public class PasswordGrantAuthenticationProvider implements AuthenticationProvider { public PasswordGrantAuthenticationProvider() {} @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { //获取自定义token信息 PasswordGrantAuthenticationToken passwordGrantAuthenticationToken = (PasswordGrantAuthenticationToken) authentication; ... ... //授权类型 AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType(); //密码 String password = (String)additionalParameters.get(OAuth2ParameterNames.PASSWORD); //用户信息 CustomUserDetails userDetails = passwordGrantAuthenticationToken.getUserDetails(); // Ensure the client is authenticated OAuth2ClientAuthenticationToken clientPrincipal = AuthUtils.getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken); ... ... // 由于在上面已验证过用户名、密码,现在构建一个已认证的对象 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password); DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() .registeredClient(registeredClient) .principal(usernamePasswordAuthenticationToken) .providerContext(ProviderContextHolder.getProviderContext()) .tokenType(OAuth2TokenType.ACCESS_TOKEN) .authorizationGrantType(authorizationGrantType) .authorizedScopes(registeredClient.getScopes()) .authorizationGrant(passwordGrantAuthenticationToken); // Initialize the OAuth2Authorization OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName(clientPrincipal.getName()) .attribute(Principal.class.getName(), usernamePasswordAuthenticationToken) .attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, registeredClient.getScopes()).authorizationGrantType(authorizationGrantType); // ----- Access token ----- OAuth2AccessToken accessToken = getAccessToken(tokenContextBuilder, authorizationBuilder); // ----- Refresh token ----- OAuth2RefreshToken refreshToken = getRefreshToken(registeredClient, clientPrincipal, tokenContextBuilder, authorizationBuilder); ... ... //存储token信息 authorizationService.save(authorization); return new PasswordTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters); } @Override public boolean supports(Class<?> authentication) { return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication); } } |
在这里,验证用户输入的username 和password的合法性。
如果合法,则生成accessToken 和refreshToken ,然后返回给用户。
PasswordRefreshTokenAuthenticationProvider
public class PasswordRefreshTokenAuthenticationProvider implements AuthenticationProvider { public PasswordRefreshTokenAuthenticationProvider() {} @Override public Authentication authenticate(Authentication authentication) { OAuth2RefreshTokenAuthenticationToken refreshTokenAuthentication = (OAuth2RefreshTokenAuthenticationToken) authentication; OAuth2ClientAuthenticationToken clientPrincipal = AuthUtils.getAuthenticatedClientElseThrowInvalidClient(refreshTokenAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); OAuth2Authorization authorization = this.authorizationService.findByToken( refreshTokenAuthentication.getRefreshToken(), OAuth2TokenType.REFRESH_TOKEN); if (authorization == null || registeredClient == null ) { log.error("Authentication或registeredClient的信息为空!"); throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode()); } if (!registeredClient.getId().equals(authorization.getRegisteredClientId()) || !registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) { log.error("registeredClient信息不符合要求!"); throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode()); } OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken(); ... ... //获取token构造时存储的用户密码认证对象 UsernamePasswordAuthenticationToken principal = authorization.getAttribute(Principal.class.getName()); DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() .registeredClient(registeredClient) .principal(principal) .providerContext(ProviderContextHolder.getProviderContext()) .authorization(authorization) .authorizedScopes(scopes) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrant(refreshTokenAuthentication); OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization); // ----- Access token ----- OAuth2AccessToken accessToken = getOAuth2AccessToken(tokenContextBuilder, authorizationBuilder); // ----- Refresh token ----- OAuth2RefreshToken currentRefreshToken = getOAuth2RefreshToken(refreshToken, registeredClient,tokenContextBuilder, authorizationBuilder); ... ... return new OAuth2AccessTokenAuthenticationToken( registeredClient, clientPrincipal, accessToken, currentRefreshToken, additionalParameters); } @Override public boolean supports(Class<?> authentication) { return OAuth2RefreshTokenAuthenticationToken.class.isAssignableFrom(authentication); } } |
在这里,验证用户输入的refreshToken 的合法性。
如果合法,则生成新的accessToken 和refreshToken返回给用户。
时序图

- 类对象主要包括:配置器对象OAuth2TokenEndpointConfigurer、过滤器对象OAuth2TokenEndpointFilter、请求参数转换器对象CustomDelegatingAuthenticationConverter、认证管理器对象ProviderManager;
- 配置器对象:使用模板方法设计模式,提供了init、beforeConfigure、configure等几个主要的过程来对过滤器进行配置;
- 过滤器对象:过滤器的过滤逻辑不仅简单也很清晰,即只包含了convert和authenticate两个过程;
- 请求参数转换器对象:框架提供了由开发人员自定义请求参数转换器的功能,请求参数转换器的主要功能是把HTTP请求参数封装为框架需要的请求参数类;
- 认证管理器对象:认证管理器管理着多个认证提供者,框架提供了由开发人员自定义认证提供者的功能。