You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

自定义Spring Authorization Server OAuth2令牌撤销端点时无法捕获处理OAuth2AuthenticationException

自定义Spring Authorization Server OAuth2令牌撤销端点时无法捕获处理OAuth2AuthenticationException

我正在尝试编写自定义的OAuth2令牌撤销端点,但遇到了一个棘手的问题:

我想在自定义的.errorResponseHandler(errorResponseHandler)中捕获并处理authenticationProvider抛出的OAuth2AuthenticationException,但无论怎么尝试都无法触发这个处理逻辑。之后我还换了思路,创建了实现AuthenticationEntryPointCustomAuthenticationEntryPoint并把它加入到filterChain里,结果还是不行。

下面是我的相关代码类:


filterChain

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@Profile("dev")
public class AuthServerConfigDev extends AuthServerConfigAbstract{
    private static final Logger logger = LoggerFactory.getLogger(AuthServerConfigDev.class);

    private final OAuth2AuthorizationService authorizationService;
    //private final TokenRevocationRepository tokenRevocationRepository;

    public AuthServerConfigDev(@Lazy OAuth2AuthorizationService authorizationService/*, TokenRevocationRepository tokenRevocationRepository*/) {
        this.authorizationService = authorizationService;
        // this.tokenRevocationRepository = tokenRevocationRepository;
    }

    @Bean  
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        
        OAuth2AuthorizationServerConfigurer authzServerConfigurer = new OAuth2AuthorizationServerConfigurer();

        authzServerConfigurer
            .oidc(withDefaults());

        authzServerConfigurer
                .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
                        .errorResponseHandler(new ClientAuthenticationFailureHandler()));

        authzServerConfigurer
                .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                        .errorResponseHandler(new ClientAuthenticationFailureHandler()));
        
        RequestMatcher endpointsMatcher = authzServerConfigurer.getEndpointsMatcher();

        http.addFilterBefore(new GrantFlowFilter(new ClientAuthenticationFailureHandler()), UsernamePasswordAuthenticationFilter.class);

        
        http
            .securityMatchers(matchers -> matchers
                .requestMatchers(
                        antMatcher(OPENAPI_JSON_URL),
                        antMatcher(SWAGGER_UI_URL),
                        antMatcher(REST_USER_PATH + "/**"),
                        antMatcher(REVOKE_ENDPOINT),
                        endpointsMatcher))
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers(HttpMethod.GET, OPENAPI_JSON_URL).hasAuthority("SCOPE.user")
                .requestMatchers(HttpMethod.GET, SWAGGER_UI_URL).hasAuthority("SCOPE.user")
                .requestMatchers(FULL_LOGIN_URL).permitAll()
                .requestMatchers(REVOKE_ENDPOINT).authenticated()
                .anyRequest().authenticated())
            .csrf(csrf -> csrf
                    .ignoringRequestMatchers(endpointsMatcher))
            .exceptionHandling(exceptions -> exceptions
                .defaultAuthenticationEntryPointFor(
                    new LoginUrlAuthenticationEntryPoint(FULL_LOGIN_URL + ParamMissingAuth),
                    new MediaTypeRequestMatcher(MediaType.TEXT_HTML)))
            .oauth2ResourceServer(resourceServer -> resourceServer.jwt(withDefaults()))
            .with(authzServerConfigurer, (authorizationServer) ->
                    authorizationServer
                            // 按照指南定义
                            .tokenRevocationEndpoint(tokenRevocationEndpoint ->
                                    tokenRevocationEndpoint
                                            .revocationRequestConverter(new CustomRevocationRequestConverter())
                                            .authenticationProvider(new CustomRevocationAuthenticationProvider(authorizationService/*, tokenRevocationRepository*/))
                                            .revocationResponseHandler(new CustomRevocationResponseHandler())
                                            .errorResponseHandler(new CustomRevocationErrorResponseHandler())));

            return http.build();
        }
}

CustomRevocationRequestConverter

public class CustomRevocationRequestConverter implements AuthenticationConverter {

    @Override
    public Authentication convert(HttpServletRequest request) {
        // 从请求参数中提取token和token_type_hint
        String tokenValue = request.getParameter("token");
        String tokenTypeHint = request.getParameter("token_type_hint");

        if (tokenValue == null || tokenValue.isEmpty()) {
            return null; // 如果token不存在,返回null
        }

        // 从SecurityContext中获取客户端认证信息
        Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();

        // 创建并返回OAuth2TokenRevocationAuthenticationToken
        if(tokenTypeHint != null && !tokenTypeHint.isEmpty())
            return new OAuth2TokenRevocationAuthenticationToken(tokenValue, clientPrincipal, tokenTypeHint);
        else {
            return new OAuth2TokenRevocationAuthenticationToken(tokenValue, clientPrincipal, null);
        }
    }
}

CustomRevocationAuthenticationProvider

public class CustomRevocationAuthenticationProvider implements AuthenticationProvider {

    private static final Logger logger = LoggerFactory.getLogger(CustomRevocationAuthenticationProvider.class);

    private final OAuth2AuthorizationService authorizationService;
    // private final TokenRevocationRepository tokenRevocationRepository;

    public CustomRevocationAuthenticationProvider(OAuth2AuthorizationService authorizationService/*, TokenRevocationRepository tokenRevocationRepository*/) {
        this.authorizationService = authorizationService;
        // this.tokenRevocationRepository = tokenRevocationRepository;
    }


    @Transactional
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 检查认证对象是否是OAuth2TokenRevocationAuthenticationToken实例
        if (authentication instanceof OAuth2TokenRevocationAuthenticationToken revocationToken) {

            // 提取token和客户端认证详情
            String tokenValue = revocationToken.getToken(); // JWT
            Authentication clientPrincipal = (Authentication) revocationToken.getPrincipal();  // 将principal强转为Authentication
            String tokenTypeHint = revocationToken.getTokenTypeHint(); // Token类型 [access_token, refresh_token]
            String principalName = clientPrincipal.getName();  // 提取principal名称

            try {
                if (validateAndRevokeToken(tokenValue, tokenTypeHint, principalName)) {
                    return new OAuth2TokenRevocationAuthenticationToken(revocationToken.getToken(), clientPrincipal, tokenTypeHint);
                }

            } catch (OAuth2AuthenticationException e) {
                // 这就是我想要用CustomRevocationErrorResponseHandler捕获处理的异常
                logger.error("{} - {}", "CustomRevocationAuthenticationProvider.authenticate", e.getError());
                throw e;
                // throw new AuthenticationServiceException(e.getError().getDescription(), e);
            } catch (Exception e) {
                // 捕获包括SQL异常在内的所有其他异常
                logger.error("{} - {}", "CustomRevocationAuthenticationProvider.authenticate", e);
                throw new OAuth2AuthenticationException(new OAuth2Error(SERVER_ERROR, "An error occurred while processing the token revocation request", null), e);
            }
        }
        return null;
    }


    // 自定义的token验证逻辑
    private boolean validateAndRevokeToken(String tokenValue, String tokenType, String principalName) {
        try {
            OAuth2Authorization authorization = authorizationService.findByToken(tokenValue, OAuth2TokenType.ACCESS_TOKEN);

            if (authorization == null) {
                return false; // Token不存在
            }

            int deletedRows;
            if(tokenType != null && tokenType.equalsIgnoreCase(OAuth2TokenType.ACCESS_TOKEN.getValue())) {
                // 撤销逻辑
                //Optional<Oauth2ClientIdDTO> oauth2ClientIdDTO = tokenRevocationRepository.findClientIdByAccessTokenValue(tokenValue);
                //validateClientIdDto(oauth2ClientIdDTO, principalName, authorization); // 如果校验失败会抛出OAuth2AuthenticationException
                //deletedRows = tokenRevocationRepository.deleteByAccessTokenValue(tokenValue);

            } else if (tokenType != null && tokenType.equalsIgnoreCase(OAuth2TokenType.REFRESH_TOKEN.getValue())) {
                // 撤销逻辑
                //Optional<Oauth2ClientIdDTO> oauth2ClientIdDTO = tokenRevocationRepository.findClientIdByRefreshTokenValue(tokenValue);
                //validateClientIdDto(oauth2ClientIdDTO, principalName, authorization); // 如果校验失败会抛出OAuth2AuthenticationException
                //deletedRows = tokenRevocationRepository.deleteByRefreshTokenValue(tokenValue);
            }
            return true;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

备注:内容来源于stack exchange,提问作者fforfabio

火山引擎 最新活动