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

如何在Spring Authorization Server中实现无需强制id_token_hint的OIDC RP注销

在Spring Authorization Server中移除OIDC注销的id_token_hint强制要求

Spring Authorization Server的默认OIDC注销实现确实强制要求id_token_hint参数,但OIDC 1.0 RP-Initiated Logout规范里这只是推荐项而非必填项。要兼容各类客户端,社区主流的做法是通过自定义认证转换器和提供者调整校验逻辑,以下是具体步骤:

1. 自定义OidcLogoutAuthenticationConverter

默认的OidcLogoutAuthenticationConverter会强制校验id_token_hint是否存在,我们可以重写这个转换器,移除该强制校验:

@Component
public class CustomOidcLogoutAuthenticationConverter implements AuthenticationConverter {

    private final OidcLogoutAuthenticationConverter delegate = new OidcLogoutAuthenticationConverter();

    @Override
    public Authentication convert(HttpServletRequest request) {
        // 先调用默认转换器处理参数
        Authentication authentication = delegate.convert(request);
        if (authentication instanceof OidcLogoutAuthenticationToken logoutToken) {
            // 允许id_token_hint为null,重新构建token
            return new OidcLogoutAuthenticationToken(
                    logoutToken.getClientRegistrationId(),
                    logoutToken.getIdTokenHint(), // 可能为null
                    logoutToken.getPostLogoutRedirectUri(),
                    logoutToken.getState()
            );
        }
        return authentication;
    }
}

或者更直接地自定义逻辑,完全跳过id_token_hint的校验:

@Component
public class CustomOidcLogoutAuthenticationConverter implements AuthenticationConverter {

    private final ClientRegistrationRepository clientRegistrationRepository;

    public CustomOidcLogoutAuthenticationConverter(ClientRegistrationRepository clientRegistrationRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
    }

    @Override
    public Authentication convert(HttpServletRequest request) {
        String clientId = request.getParameter("client_id");
        if (clientId == null) {
            return null; // 至少校验client_id,保证基本安全
        }
        ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId(clientId);
        if (clientRegistration == null) {
            return null;
        }

        return new OidcLogoutAuthenticationToken(
                clientId,
                request.getParameter("id_token_hint"), // 允许为null
                request.getParameter("post_logout_redirect_uri"),
                request.getParameter("state")
        );
    }
}

2. 自定义OidcLogoutAuthenticationProvider

转换器放行无id_token_hint的请求后,需要修改认证提供者逻辑,让它能处理这种情况。默认的OidcLogoutAuthenticationProvider会在id_token_hint为null时抛出异常,我们可以扩展它:

@Component
public class CustomOidcLogoutAuthenticationProvider extends OidcLogoutAuthenticationProvider {

    private final SessionRegistry sessionRegistry; // 用于通过session获取用户信息

    public CustomOidcLogoutAuthenticationProvider(
            JwtDecoderFactory<ClientRegistration> jwtDecoderFactory,
            SessionRegistry sessionRegistry) {
        super(jwtDecoderFactory);
        this.sessionRegistry = sessionRegistry;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        OidcLogoutAuthenticationToken logoutToken = (OidcLogoutAuthenticationToken) authentication;
        if (logoutToken.getIdTokenHint() == null) {
            // 无id_token_hint时,通过session获取当前认证用户
            Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
            if (currentUser != null && currentUser.isAuthenticated()) {
                // 这里可添加逻辑:根据当前用户和client_id清理会话
                // 比如从sessionRegistry中移除相关会话
                return logoutToken;
            }
            // 若没有当前用户,可根据业务需求决定是否允许注销
            return logoutToken;
        }
        // 有id_token_hint时,走默认逻辑
        return super.authenticate(authentication);
    }
}

3. 替换默认组件到授权服务器配置

在AuthorizationServerConfigurer中替换默认的转换器和提供者:

@Configuration
public class AuthorizationServerConfig implements AuthorizationServerConfigurer {

    private final CustomOidcLogoutAuthenticationConverter customLogoutConverter;
    private final CustomOidcLogoutAuthenticationProvider customLogoutProvider;

    public AuthorizationServerConfig(
            CustomOidcLogoutAuthenticationConverter customLogoutConverter,
            CustomOidcLogoutAuthenticationProvider customLogoutProvider) {
        this.customLogoutConverter = customLogoutConverter;
        this.customLogoutProvider = customLogoutProvider;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .logoutEndpoint(logout -> logout
                        .authenticationConverter(customLogoutConverter)
                        .authenticationProvider(customLogoutProvider)
                );
    }

    // 其他配置...
}

注意事项

  • 即使允许无id_token_hint,也必须校验client_id的合法性,避免恶意请求。
  • 无id_token_hint时,建议通过当前会话(SecurityContext)关联用户,确保注销操作的安全性,避免误注销他人会话。
  • 如果客户端传递了post_logout_redirect_uri,要校验该URI是否在客户端配置的允许列表中,这部分默认逻辑已经处理,无需额外修改。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:03:25