Spring Security多OIDC提供商同时登录及状态校验方案咨询
基于Spring Security实现多OIDC提供商兼容及跨提供商登录校验
当然可以通过Spring Security实现该需求,以下是具体配置方案和校验逻辑:
一、多OIDC提供商配置调整
核心是给每个OIDC提供商分配唯一的registrationId,并配置会话管理支持同一用户关联多个身份认证。
示例SecurityConfig配置
@Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) // 配置双OIDC提供商登录支持 .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(endpoint -> endpoint .baseUri("/oauth2/authorization") ) .clientRegistrationRepository(clientRegistrationRepository()) ) // 允许单会话关联多身份认证 .sessionManagement(session -> session .maximumSessions(1) .sessionRegistry(sessionRegistry()) ); return http.build(); } @Bean public ClientRegistrationRepository clientRegistrationRepository() { return new InMemoryClientRegistrationRepository( // 配置OIDC_PROVIDER_1 ClientRegistrations.fromOidcIssuerLocation("https://oidc-provider-1.com/issuer") .clientId("app2-oidc1-client-id") .clientSecret("app2-oidc1-client-secret") .registrationId("oidc1") .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile", "app1-access") // 包含访问APP_1所需权限 .build(), // 配置OIDC_PROVIDER_2 ClientRegistrations.fromOidcIssuerLocation("https://oidc-provider-2.com/issuer") .clientId("app2-oidc2-client-id") .clientSecret("app2-oidc2-client-secret") .registrationId("oidc2") .redirectUri("{baseUrl}/login/oauth2/code/{registrationId}") .scope("openid", "profile") .build() ); } @Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } }
配置说明
- 每个OIDC提供商通过
registrationId(oidc1/oidc2)区分,Spring Security会自动生成对应的授权入口:/oauth2/authorization/oidc1和/oauth2/authorization/oidc2 - 会话管理配置允许同一用户在单会话下关联多个身份认证,用户通过OIDC_PROVIDER_2登录后,可再触发OIDC_PROVIDER_1的授权流程,无需退出当前会话
二、校验用户是否已登录OIDC_PROVIDER_1
可以通过OAuth2AuthenticationToken判断用户是否完成OIDC_PROVIDER_1的认证,以下是两种实现方式:
1. 控制器内直接校验
@Controller public class AuthCheckController { private final OAuth2AuthorizedClientService authorizedClientService; public AuthCheckController(OAuth2AuthorizedClientService authorizedClientService) { this.authorizedClientService = authorizedClientService; } @GetMapping("/check-oidc1-auth") public String checkOidc1Authentication(Authentication authentication) { // 判断当前认证是否来自OIDC_PROVIDER_1 if (authentication instanceof OAuth2AuthenticationToken token) { if ("oidc1".equals(token.getAuthorizedClientRegistrationId())) { // 获取访问APP_1的令牌 OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient( "oidc1", authentication.getName() ); String app1AccessToken = client.getAccessToken().getTokenValue(); // 后续可将令牌传递给APP_1或存储使用 return "redirect:/app1-access?token=" + app1AccessToken; } } // 未通过OIDC_PROVIDER_1认证,跳转至其授权页 return "redirect:/oauth2/authorization/oidc1"; } }
2. 自定义权限表达式全局校验
@Bean public MethodSecurityExpressionHandler methodSecurityExpressionHandler() { DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); handler.setPermissionEvaluator(new Oidc1PermissionEvaluator()); return handler; } public class Oidc1PermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object target, Object permission) { if ("hasOidc1Login".equals(permission)) { return authentication.getAuthorities().stream() .anyMatch(auth -> auth instanceof OAuth2AuthenticationToken token && "oidc1".equals(token.getAuthorizedClientRegistrationId())); } return false; } @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return hasPermission(authentication, null, permission); } }
之后可在接口方法上直接使用权限校验:
@PreAuthorize("hasPermission(null, 'hasOidc1Login')") public String accessApp1Resource() { // 访问APP_1资源的逻辑 }
三、关键注意事项
- 确保两个OIDC提供商的用户标识(如
subject字段)可对应同一用户,否则Spring Security会判定为不同用户,导致会话冲突;若标识不一致,可通过自定义OAuth2UserService统一用户信息 - 需单独处理OIDC_PROVIDER_1令牌的有效期和刷新逻辑,保证访问APP_1的权限始终有效
- 可自定义登录页,提供两个提供商的登录按钮,优化用户操作体验
内容的提问来源于stack exchange,提问作者Neo
相关产品推荐
相关产品推荐

