You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Authorization Server撤销access token报invalid_client错误

问题描述

当前使用 Spring Authorization Server 0.3.1 版本,通过OAuth客户端(angular-oauth2-oidc)调用 /oauth2/revoke 端点,请求载荷如下:

client_id: my-client-id
token: eyJraWQiOiJlYzA3N2Y2OC1jMjQ1LTQ[the rest is stripped for readability]
token_type_hint: access_token

接口响应返回错误invalid_client。

经过调试定位,异常在org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider#authenticate方法中抛出,相关代码如下:

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication =
            (OAuth2TokenRevocationAuthenticationToken) authentication;

    OAuth2ClientAuthenticationToken clientPrincipal =
            getAuthenticatedClientElseThrowInvalidClient(tokenRevocationAuthentication);

其中getAuthenticatedClientElseThrowInvalidClient方法的实现逻辑如下:

static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
    OAuth2ClientAuthenticationToken clientPrincipal = null;
    if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
        clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
    }
    if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
        return clientPrincipal;
    }
    throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}

经分析,OAuth2TokenRevocationAuthenticationProvider要求传入的authentication对象的principal属性为OAuth2ClientAuthenticationToken实例,但实际运行时principal是JwtAuthenticationToken实例。排查发现,由于请求携带了bearer token,服务端完成认证后会向SecurityContextHolder中设置JwtAuthenticationToken,相关调试日志如下:

2022-06-28 23:27:01.392 DEBUG 16904 --- [  XNIO-1 task-1] o.s.security.web.FilterChainProxy        : Securing POST /oauth2/revoke
2022-06-28 23:27:01.393 DEBUG 16904 --- [  XNIO-1 task-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-06-28 23:27:01.394 DEBUG 16904 --- [  XNIO-1 task-1] o.s.s.o.s.r.a.JwtAuthenticationProvider  : Authenticated token
2022-06-28 23:27:01.397 DEBUG 16904 --- [  XNIO-1 task-1] .o.s.r.w.BearerTokenAuthenticationFilter : Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@1615fb2d, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[SCOPE_openid, SCOPE_profile, SCOPE_offline_access, SCOPE_email]]
2022-06-28 23:27:01.398 DEBUG 16904 --- [  XNIO-1 task-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Authorized filter invocation [POST /oauth2/revoke] with attributes [authenticated]

该场景下是否遗漏了相关配置?如何调整才能让/oauth2/revoke端点获取到OAuth2ClientAuthenticationToken而非JwtAuthenticationToken?


回答

问题根因

invalid_client错误核心是安全过滤链的配置顺序与拦截规则错误:

  • /oauth2/revoke是OAuth2协议定义的令牌撤销端点,Spring Authorization Server对该端点的鉴权逻辑要求必须先完成客户端身份认证,从SecurityContext中获取OAuth2ClientAuthenticationToken类型的已认证客户端主体。
  • 当前配置将/oauth2/revoke纳入了普通资源服务的JWT认证规则范围,请求携带用户access_token时,BearerTokenAuthenticationFilter优先执行,直接将代表用户身份的JwtAuthenticationToken写入SecurityContext,后续客户端认证逻辑完全不触发,自然无法获取到要求的客户端认证对象。
    从贴出的调试日志可以直接验证:请求到达端点前仅完成了JWT用户认证,没有任何客户端认证流程的执行记录。

修复步骤

1. 调整多安全过滤链优先级

Spring Authorization Server会自带处理OAuth2协议端点的过滤链,必须保证该过滤链优先级高于自定义的资源服务过滤链,避免资源服务的JWT认证逻辑提前拦截授权服务器端点,示例配置如下:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE) // 授权服务器过滤链设置为最高优先级
public SecurityFilterChain authorizationServerSecurityChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
            new OAuth2AuthorizationServerConfigurer();
    http.apply(authorizationServerConfigurer);
    // 此处可追加授权服务器的自定义配置,比如客户端存储、令牌生成规则、端点权限配置等
    return http.build();
}

@Bean
@Order(2) // 资源服务过滤链优先级低于授权服务器链
public SecurityFilterChain resourceServerSecurityChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorize -> authorize
            // 不要在此处将/oauth2/**下的端点配置为需要JWT认证
            .anyRequest().authenticated()
        )
        .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
    return http.build();
}

2. 修正撤销端点的请求方式

当前请求仅传递了client_id表单参数,缺少客户端认证凭证,且不要在请求中携带用户的Bearer Token作为Authorization头,避免被JWT认证过滤器拦截:

  • 如果使用client_secret_post客户端认证方式:需要在表单参数中额外传递client_secret,请求不携带Authorization头
  • 如果使用client_secret_basic客户端认证方式:将client_id和client_secret编码后放入Basic类型的Authorization请求头,不需要在表单中重复传递客户端凭证

3. 校验客户端注册配置

确认授权服务器中注册的my-client-id客户端,配置的认证方式和实际调用时使用的方式一致:比如使用client_secret_post调用时,客户端的clientAuthenticationMethods必须包含CLIENT_SECRET_POST。

验证

调整配置后重启服务,不携带用户Bearer Token、按选定的客户端认证方式传递凭证调用撤销端点,调试日志会出现ClientSecretAuthenticationProvider的认证记录,SecurityContext中会存入OAuth2ClientAuthenticationToken,接口即可正常响应,不再返回invalid_client错误。


内容的提问来源于stack exchange,提问作者Andrey Grigoriev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 15:24:26