spring Oauth2-Authorization-Server client_secret_basic流程

湛光华
2023-12-01

Spring Oauth2-Authorization-Server client_secret_basic 过程

基于 spring-security-oauth2-authorization-server 0.2.3

OAuth2ClientAuthenticationFilter

对 client_id 和 client_secret 进行认证,目前支持四种:

  • JwtClientAssertionAuthenticationConverter: Jwt 方式
  • ClientSecretBasicAuthenticationConverter: 将 Basic Base64.encode(client_id:client_secret) 放在 Authorization 头部解析
  • ClientSecretPostAuthenticationConverter: POST 表单方式,将 client_id 和 client_secret 明文放在表单中
  • PublicClientAuthenticationConverter: Proof Key for Code Exchange,没研究

以 ClientSecretBasicAuthenticationConverter 为例

public OAuth2ClientAuthenticationFilter(AuthenticationManager authenticationManager,
                                        RequestMatcher requestMatcher) {
    Assert.notNull(authenticationManager, "authenticationManager cannot be null");
    Assert.notNull(requestMatcher, "requestMatcher cannot be null");
    this.authenticationManager = authenticationManager;
    this.requestMatcher = requestMatcher;
    this.authenticationConverter = new DelegatingAuthenticationConverter(
        Arrays.asList(
            new JwtClientAssertionAuthenticationConverter(),
            new ClientSecretBasicAuthenticationConverter(),
            new ClientSecretPostAuthenticationConverter(),
            new PublicClientAuthenticationConverter()));
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    if (!this.requestMatcher.matches(request)) {
        filterChain.doFilter(request, response);
        return;
    }

    try {
        Authentication authenticationRequest = this.authenticationConverter.convert(request);
        if (authenticationRequest instanceof AbstractAuthenticationToken) {
            ((AbstractAuthenticationToken) authenticationRequest).setDetails(
                this.authenticationDetailsSource.buildDetails(request));
        }
        if (authenticationRequest != null) {
            Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
            this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
        }
        filterChain.doFilter(request, response);

    } catch (OAuth2AuthenticationException ex) {
        this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
    }
}

private void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                     Authentication authentication) {

    SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
    securityContext.setAuthentication(authentication);
    SecurityContextHolder.setContext(securityContext);
}

这里 onAuthenticationSuccess 很重要, 将认证的结果放在 SecurityContextHolder 上下文中,传递给后面的 filter

ClientSecretBasicAuthenticationConverter:

public final class ClientSecretBasicAuthenticationConverter implements AuthenticationConverter {

    @Nullable
    @Override
    public Authentication convert(HttpServletRequest request) {
        String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (header == null) {
            return null;
        }

        String[] parts = header.split("\\s");
        if (!parts[0].equalsIgnoreCase("Basic")) {
            return null;
        }

        if (parts.length != 2) {
            throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
        }

        byte[] decodedCredentials;
        try {
            decodedCredentials = Base64.getDecoder().decode(
                parts[1].getBytes(StandardCharsets.UTF_8));
        } catch (IllegalArgumentException ex) {
            throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex);
        }

        String credentialsString = new String(decodedCredentials, StandardCharsets.UTF_8);
        String[] credentials = credentialsString.split(":", 2);
        if (credentials.length != 2 ||
            !StringUtils.hasText(credentials[0]) ||
            !StringUtils.hasText(credentials[1])) {
            throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
        }

        String clientID;
        String clientSecret;
        try {
            clientID = URLDecoder.decode(credentials[0], StandardCharsets.UTF_8.name());
            clientSecret = URLDecoder.decode(credentials[1], StandardCharsets.UTF_8.name());
        } catch (Exception ex) {
            throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex);
        }

        return new OAuth2ClientAuthenticationToken(clientID, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, clientSecret,
                                                   OAuth2EndpointUtils.getParametersIfMatchesAuthorizationCodeGrantRequest(request));
    }

}

比较简单: 从 AUTHORIZATION 提取 client_id 和 client_secret, 至此, client_id 认证结束

OAuth2TokenEndpointFilter

拦截 /oauth2/token 请求

public OAuth2TokenEndpointFilter(AuthenticationManager authenticationManager, String tokenEndpointUri) {
    Assert.notNull(authenticationManager, "authenticationManager cannot be null");
    Assert.hasText(tokenEndpointUri, "tokenEndpointUri cannot be empty");
    this.authenticationManager = authenticationManager;
    this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
    this.authenticationConverter = new DelegatingAuthenticationConverter(
        Arrays.asList(
            new OAuth2AuthorizationCodeAuthenticationConverter(),
            new OAuth2RefreshTokenAuthenticationConverter(),
            new OAuth2ClientCredentialsAuthenticationConverter()));
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    throws ServletException, IOException {

    if (!this.tokenEndpointMatcher.matches(request)) {
        filterChain.doFilter(request, response);
        return;
    }

    try {
        String[] grantTypes = request.getParameterValues(OAuth2ParameterNames.GRANT_TYPE);
        if (grantTypes == null || grantTypes.length != 1) {
            throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.GRANT_TYPE);
        }

        Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request);
        if (authorizationGrantAuthentication == null) {
            throwError(OAuth2ErrorCodes.UNSUPPORTED_GRANT_TYPE, OAuth2ParameterNames.GRANT_TYPE);
        }
        if (authorizationGrantAuthentication instanceof AbstractAuthenticationToken) {
            ((AbstractAuthenticationToken) authorizationGrantAuthentication)
            .setDetails(this.authenticationDetailsSource.buildDetails(request));
        }

        OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
            (OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
        this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication);
    } catch (OAuth2AuthenticationException ex) {
        SecurityContextHolder.clearContext();
        this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
    }
}

// 认证成功后,直接将token 返回
private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response,
                                     Authentication authentication) throws IOException {

    OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
        (OAuth2AccessTokenAuthenticationToken) authentication;

    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);
    }
    OAuth2AccessTokenResponse accessTokenResponse = builder.build();
    ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
    this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
}


  • OAuth2TokenEndpointFilter
    • OAuth2TokenEndpointFilter
      • OAuth2ClientCredentialsAuthenticationConverter: 从request 中检出 client_id和 client_secret
    • AuthenticationManager
      • ProviderManager
        • OAuth2ClientCredentialsAuthenticationProvider: 认证 client_id 和 client_secret
          • OAuth2TokenGenerator: 生成 token
          • OAuth2AuthorizationService: 保存 token
 类似资料: