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. 验证撤销是否生效
你可以通过以下方式确认功能正常:
- 登录应用后,获取当前用户的刷新令牌(可以通过调试或查看IdP的用户令牌列表)。
- 执行登出操作。
- 尝试用刚才的刷新令牌去IdP请求新的访问令牌,应该会被拒绝(通常返回
invalid_token或类似错误)。 - 查看IdP的日志,确认收到了来自你的应用的撤销请求。
内容的提问来源于stack exchange,提问作者piraces
相关产品推荐
相关产品推荐

