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

Spring Security:如何在用户登出时撤销刷新令牌?

如何用Spring Security实现登出时自动撤销刷新令牌(遵循RFC7009)

好问题!其实Spring Security 5及以上版本已经原生支持OAuth 2.0令牌撤销(完全遵循RFC7009标准),完全不需要依赖已弃用的Spring Security OAuth项目,而且可以实现自动处理——不用你手动构造HTTP请求去调用身份提供商(IdP)的撤销端点。下面一步步给你讲怎么实现:

1. 先配置客户端的令牌撤销端点

首先,你需要在应用配置里指定IdP的令牌撤销端点,Spring Security会自动识别这个配置并使用它来处理撤销逻辑。以application.yml为例:

spring:
  security:
    oauth2:
      client:
        registration:
          my-idp-client:
            client-id: 你的客户端ID
            client-secret: 你的客户端密钥
            # 其他必要配置(比如授权类型、跳转URI等)
        provider:
          my-idp-provider:
            token-uri: https://你的IdP地址/oauth2/token
            revocation-uri: https://你的IdP地址/oauth2/revoke  # 核心:必须配置这个符合RFC7009的撤销端点
            # 其他配置(授权端点、用户信息端点等)

注意:一定要确认你的IdP支持RFC7009的撤销端点,像Keycloak、Okta、Auth0这些主流IdP都默认支持。

2. 自定义登出成功处理器触发撤销

Spring Security的默认登出流程只会清理本地会话,不会主动去IdP撤销刷新令牌。所以我们需要扩展LogoutSuccessHandler,在用户登出成功后自动触发令牌撤销:

import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.revocation.DefaultOAuth2TokenRevoker;
import org.springframework.security.oauth2.client.revocation.OAuth2TokenRevoker;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class OAuth2LogoutSuccessHandler implements LogoutSuccessHandler {

    private final OAuth2AuthorizedClientService authorizedClientService;
    private final OAuth2TokenRevoker tokenRevoker;

    public OAuth2LogoutSuccessHandler(OAuth2AuthorizedClientService authorizedClientService,
                                      ClientRegistrationRepository clientRegistrationRepository) {
        this.authorizedClientService = authorizedClientService;
        // 用默认的令牌撤销器,它会自动根据配置的revocation-uri发送请求
        this.tokenRevoker = new DefaultOAuth2TokenRevoker(authorizedClientService, clientRegistrationRepository);
    }

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // 只处理OAuth2认证的用户
        if (authentication instanceof OAuth2AuthenticationToken oauth2AuthToken) {
            String clientRegistrationId = oauth2AuthToken.getAuthorizedClientRegistrationId();
            String userName = oauth2AuthToken.getName();

            // 获取当前用户的授权客户端信息(包含刷新令牌)
            OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(
                    clientRegistrationId, userName);

            if (authorizedClient != null && authorizedClient.getRefreshToken() != null) {
                // 自动触发撤销:DefaultOAuth2TokenRevoker会帮你调用IdP的撤销端点
                tokenRevoker.revoke(authorizedClient);
            }
        }

        // 登出完成后重定向到你想要的页面(比如首页或登录页)
        response.sendRedirect("/");
    }
}

然后把这个处理器注册到Spring Security的配置里:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

@Configuration
public class SecurityConfig {

    private final LogoutSuccessHandler oAuth2LogoutSuccessHandler;

    public SecurityConfig(LogoutSuccessHandler oAuth2LogoutSuccessHandler) {
        this.oAuth2LogoutSuccessHandler = oAuth2LogoutSuccessHandler;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                // 这里可以配置你的OAuth2登录相关设置(比如跳转URI、用户信息处理器等)
            )
            .logout(logout -> logout
                .logoutSuccessHandler(oAuth2LogoutSuccessHandler)
                .invalidateHttpSession(true) // 清理本地会话
                .deleteCookies("JSESSIONID") // 删除会话Cookie
            );

        return http.build();
    }
}

3. 几个关键注意点

  • 客户端权限:确保你的客户端在IdP那边被允许执行令牌撤销操作,保密客户端需要正确配置client-secret,公共客户端(比如SPA)可能需要IdP额外的配置支持。
  • 授权客户端存储:默认的InMemoryOAuth2AuthorizedClientService只适合开发环境,生产环境建议换成基于数据库的实现(比如JdbcOAuth2AuthorizedClientService),确保用户的授权信息能持久化存储。
  • 异常处理:如果需要处理撤销失败的情况,可以在tokenRevoker.revoke()周围加try-catch块,根据IdP返回的错误码做相应处理(比如记录日志、给用户提示等)。

4. 验证撤销是否生效

你可以通过以下方式确认功能正常:

  1. 登录应用后,获取当前用户的刷新令牌(可以通过调试或查看IdP的用户令牌列表)。
  2. 执行登出操作。
  3. 尝试用刚才的刷新令牌去IdP请求新的访问令牌,应该会被拒绝(通常返回invalid_token或类似错误)。
  4. 查看IdP的日志,确认收到了来自你的应用的撤销请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 09:38:13