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

Spring Authorization Server登出时会话未删除的问题排查

问题诊断与修复方案

核心问题分析

  1. 登出端点配置错位:/connect/logout是Spring Authorization Server的OIDC标准登出端点,当前被优先级更高的authorizationSecurityFilterChain拦截,但该过滤器链未配置登出逻辑,导致请求未触发会话清理与令牌失效操作。
  2. 自包含JWT的无状态特性:你配置了accessTokenFormat=SELF_CONTAINED,这类JWT是无状态的,资源服务器仅校验签名和过期时间,不会主动向授权服务器校验令牌是否已被撤销,因此即使登出,未过期的JWT仍能访问资源。
  3. OAuth2授权记录未清理:登出时未删除数据库中存储的OAuth2Authorization记录,导致令牌关联的授权状态仍有效。

具体修复步骤

1. 将登出配置迁移到授权服务器过滤器链

修改authorizationSecurityFilterChain,添加登出逻辑,确保/connect/logout请求被正确处理:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationSecurityFilterChain(HttpSecurity http, 
                                                           ClientRegistrationRepository clientRegistrationRepository,
                                                           OAuth2AuthorizationService authorizationService,
                                                           SessionRegistry sessionRegistry) throws Exception {

    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .oidc(Customizer.withDefaults());

    http
            .sessionManagement(sessionManagement ->
                    sessionManagement
                            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                            .maximumSessions(1)
                            .sessionRegistry(sessionRegistry)
            )
            .exceptionHandling((exceptions) -> exceptions
                    .defaultAuthenticationEntryPointFor(
                            new LoginUrlAuthenticationEntryPoint("/oauth2/authorization/keycloak"),
                            new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
                    )
            )
            .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()))
            // 新增登出配置
            .logout(logoutConfigurer -> logoutConfigurer
                    .logoutUrl("/connect/logout")
                    .clearAuthentication(true)
                    .invalidateHttpSession(true)
                    .deleteCookies("JSESSIONID")
                    .logoutSuccessHandler(oidcLogoutSuccessHandler(clientRegistrationRepository))
                    // 添加授权记录清理Handler
                    .addLogoutHandler(new OAuth2AuthorizationLogoutHandler(authorizationService))
                    // 添加会话注册表清理Handler
                    .addLogoutHandler((request, response, auth) -> {
                        String sessionId = request.getSession(false)?.getId();
                        if (sessionId != null) {
                            sessionRegistry.removeSessionInformation(sessionId);
                        }
                    })
            );
    return http.build();
}

同时删除defaultSecurityFilterChain中的登出配置,避免冲突。

2. 解决自包含JWT无法主动撤销的问题

有两种方案可选:

方案A:切换为引用令牌(推荐)

修改RegisteredClient的tokenSettings,将令牌格式改为引用类型,资源服务器会主动向授权服务器校验令牌有效性:

.tokenSettings(TokenSettings.builder()
        .accessTokenTimeToLive(Duration.ofMinutes(15)) // 缩短有效期提升安全性
        .refreshTokenTimeToLive(Duration.ofDays(8))
        .reuseRefreshTokens(false)
        .accessTokenFormat(OAuth2TokenFormat.REFERENCE) // 改为引用令牌
        .x509CertificateBoundAccessTokens(true)
        .build())

方案B:配置资源服务器使用令牌 introspection(兼容自包含JWT)

在资源服务器的安全配置中,添加令牌校验端点配置:

@Bean
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(authorize -> authorize
                    .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                    .opaqueToken(opaqueToken -> opaqueToken
                            .introspectionUri("http://localhost:8080/oauth2/introspect")
                            .introspectionClientCredentials("example-client", "password")
                    )
            );
    return http.build();
}

3. 前端登出流程修正

前端需遵循OIDC标准登出流程,携带id_token_hint参数调用登出端点,确保授权服务器识别当前用户:

// 前端登出示例代码
const logout = async () => {
  const idToken = localStorage.getItem('id_token');
  const logoutUrl = `http://localhost:8080/connect/logout?id_token_hint=${idToken}&post_logout_redirect_uri=http://localhost:3000/login`;
  window.location.href = logoutUrl;
};

验证方法

  1. 登出后检查数据库oauth2_authorization表,确认对应用户的授权记录已被删除。
  2. 使用旧的access token调用资源服务器接口,应返回401 Unauthorized或403 Forbidden。
  3. 检查浏览器JSESSIONID cookie已被清除,前端自动跳转到登录页。

内容的提问来源于stack exchange,提问作者Reham.Dev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 06:05:55