如何在Spring Authorization Server中实现无需强制id_token_hint的OIDC RP注销
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
相关产品推荐
相关产品推荐

