目錄
示例
大體流程
配置器
OAuth2AuthorizationServerConfigurer
createConfigurers
???????configure
過(guò)濾器
???????OAuth2TokenEndpointFilter
???????自定義配置
配置示例
???????tokenEndpoint
???????自定義請(qǐng)求參數(shù)轉(zhuǎn)換器
CustomDelegatingAuthenticationConverter
???????PasswordGrantAuthenticationConverter
???????PasswordRefreshTokenAuthenticationConverter
自定義認(rèn)證提供者
PasswordGrantAuthenticationProvider
???????PasswordRefreshTokenAuthenticationProvider
時(shí)序圖
? ? ? ? 本篇文章我們來(lái)研究spring-security5框架如何實(shí)現(xiàn)OAuth2的密碼授權(quán)模式,即用戶向客戶端提供自己的用戶名和密碼,客戶端使用這些信息向“服務(wù)提供商”索要授權(quán)。
? ? ? ? 先從一個(gè)簡(jiǎn)單示例開始,如下圖所示:
示例
? ? ? ? 首先,新建一個(gè)config包用于存放spring-security通用配置;
? ? ? ? 然后,新建一個(gè)AuthSecurityConfig類,給AuthSecurityConfig類中加上@EnableWebSecurity 注解后,這樣便會(huì)自動(dòng)被 Spring發(fā)現(xiàn)并注冊(cè)。
@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(); ??} |
? ? ? ? 在這里,首先實(shí)例化一個(gè)配置器OAuth2AuthorizationServerConfigurer對(duì)象;
? ? ? ? 然后,自定義accessTokenRequestConverter和authenticationProvider配置信息;
? ? ? ? 最后,將該配置器對(duì)象應(yīng)用到HttpSecurity 對(duì)象即可。
大體流程
? ? ? ? 點(diǎn)擊示例里的OAuth2AuthorizationServerConfigurer類,如下所示:
???????配置器
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>> extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> { private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers(); |
? ? ? ? 在這里,調(diào)用createConfigurers()方法,創(chuàng)建各種相關(guān)的子配置器對(duì)象。
? ? ? ? 點(diǎn)擊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; } |
? ? ? ? 在這里,我們看到程序創(chuàng)建了配置器OAuth2TokenEndpointConfigurer對(duì)象。配置器對(duì)象創(chuàng)建好了之后,重點(diǎn)要關(guān)注該對(duì)象的configure()方法。
? ? ? ? 點(diǎn)擊對(duì)象的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); } ... } |
? ? ? ?在這里,我們看到程序創(chuàng)建了過(guò)濾器OAuth2TokenEndpointFilter對(duì)象,并且傳入請(qǐng)求參數(shù)轉(zhuǎn)換器對(duì)象accessTokenRequestConverter和認(rèn)證管理器對(duì)象authenticationManager。
? ? ? ? 過(guò)濾器對(duì)象如下所示:
過(guò)濾器
???????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 { ... ... } } |
? ? ? ? 在這里,我們看到整個(gè)過(guò)濾邏輯主要包括請(qǐng)求參數(shù)轉(zhuǎn)換操作convert(request)和認(rèn)證操作authenticate(authorizationGrantAuthentication)兩個(gè)。
? ? ? ? 請(qǐng)求參數(shù)轉(zhuǎn)換操作由authenticationConverter對(duì)象來(lái)處理,該對(duì)象在實(shí)例化過(guò)濾器時(shí)傳入。
? ? ? ? 認(rèn)證操作由authenticationManager對(duì)象處理,該對(duì)象在實(shí)例化過(guò)濾器時(shí)傳入。
? ? ? ? 具體實(shí)現(xiàn)邏輯見后續(xù)章節(jié)。
???????自定義配置
配置示例
@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(); ??} |
? ? ? ?在這里,通過(guò)調(diào)用tokenEndpoint()方法實(shí)現(xiàn)對(duì)請(qǐng)求參數(shù)轉(zhuǎn)換器和認(rèn)證提供者的自定義。傳入的匿名內(nèi)部類,采用鏈?zhǔn)秸{(diào)用的方式先自定義請(qǐng)求參數(shù)轉(zhuǎn)換器CustomDelegatingAuthenticationConverter,再自定義認(rèn)證提供者PasswordGrantAuthenticationProvider和PasswordRefreshTokenAuthenticationProvider。
???????tokenEndpoint
public OAuth2AuthorizationServerConfigurer<B> tokenEndpoint(Customizer<OAuth2TokenEndpointConfigurer> tokenEndpoint(Customizer) { tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class)); return this; } |
? ? ? ?在這里,通過(guò)調(diào)用getConfigurer()方法獲取配置器實(shí)例OAuth2TokenEndpointConfigurer,然后將該實(shí)例傳入給匿名內(nèi)部類的接口方法。
???????自定義請(qǐng)求參數(shù)轉(zhuǎn)換器
CustomDelegatingAuthenticationConverter
? ? ? ? 這是一個(gè)轉(zhuǎn)換器的代表類,代表如下兩個(gè)轉(zhuǎn)換器類: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()); ????} } |
? ? ? ?在這里,按順序調(diào)用每個(gè)轉(zhuǎn)換器實(shí)例的convert()方法,只要調(diào)用結(jié)果返回不為null則表示調(diào)用成功,成功則結(jié)束convert操作;如果遍歷所有的轉(zhuǎn)換器都沒有調(diào)用成功,則拋出異常。
???????PasswordGrantAuthenticationConverter
public class PasswordGrantAuthenticationConverter implements AuthenticationConverter { ????public PasswordGrantAuthenticationConverter() { } ????@Override ????public Authentication convert(HttpServletRequest request) { ????????// 判斷登錄授權(quán)模式是否是支持的類型 ????????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(); ????????// 獲取用戶名與密碼,并校驗(yàn)密碼的合法性 ????????String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); ????????// 獲取用戶信息 ????????CustomUserDetails userDetails = customUserDetailsService.loadUser(username, password); ????????// 返回自定義的PasswordGrantAuthenticationToken對(duì)象 ????????return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters, ????????????????, userDetails); ????} } |
? ? ? ? 在這里,將用戶輸入的username 和password 轉(zhuǎn)換為Authentication對(duì)象。
???????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 轉(zhuǎn)換為Authentication對(duì)象。
自定義認(rèn)證提供者
PasswordGrantAuthenticationProvider
public class PasswordGrantAuthenticationProvider implements AuthenticationProvider { ????public PasswordGrantAuthenticationProvider() {} ????@Override ????public Authentication authenticate(Authentication authentication) throws AuthenticationException { ????????//獲取自定義token信息 ????????PasswordGrantAuthenticationToken passwordGrantAuthenticationToken = ????????????????(PasswordGrantAuthenticationToken) authentication; ????????... ... ????????//授權(quán)類型 ????????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); ????????... ... ????????// 由于在上面已驗(yàn)證過(guò)用戶名、密碼,現(xiàn)在構(gòu)建一個(gè)已認(rèn)證的對(duì)象 ????????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); ????????... ... ????????//存儲(chǔ)token信息 ????????authorizationService.save(authorization); ????????return new PasswordTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters); ????} ????@Override ????public boolean supports(Class<?> authentication) { ????????return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication); ????} } |
? ? ? ? 在這里,驗(yàn)證用戶輸入的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構(gòu)造時(shí)存儲(chǔ)的用戶密碼認(rèn)證對(duì)象 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); } } |
? ? ? ? 在這里,驗(yàn)證用戶輸入的refreshToken 的合法性。
? ? ? ? 如果合法,則生成新的accessToken 和refreshToken返回給用戶。
時(shí)序圖

- 類對(duì)象主要包括:配置器對(duì)象OAuth2TokenEndpointConfigurer、過(guò)濾器對(duì)象OAuth2TokenEndpointFilter、請(qǐng)求參數(shù)轉(zhuǎn)換器對(duì)象CustomDelegatingAuthenticationConverter、認(rèn)證管理器對(duì)象ProviderManager;
- 配置器對(duì)象:使用模板方法設(shè)計(jì)模式,提供了init、beforeConfigure、configure等幾個(gè)主要的過(guò)程來(lái)對(duì)過(guò)濾器進(jìn)行配置;
- 過(guò)濾器對(duì)象:過(guò)濾器的過(guò)濾邏輯不僅簡(jiǎn)單也很清晰,即只包含了convert和authenticate兩個(gè)過(guò)程;
- 請(qǐng)求參數(shù)轉(zhuǎn)換器對(duì)象:框架提供了由開發(fā)人員自定義請(qǐng)求參數(shù)轉(zhuǎn)換器的功能,請(qǐng)求參數(shù)轉(zhuǎn)換器的主要功能是把HTTP請(qǐng)求參數(shù)封裝為框架需要的請(qǐng)求參數(shù)類;
- 認(rèn)證管理器對(duì)象:認(rèn)證管理器管理著多個(gè)認(rèn)證提供者,框架提供了由開發(fā)人員自定義認(rèn)證提供者的功能。