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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 07:25:24