目录
 
示例
 
大体流程
 
配置器
 
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请求参数封装为框架需要的请求参数类;
 - 认证管理器对象:认证管理器管理着多个认证提供者,框架提供了由开发人员自定义认证提供者的功能。