You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何配置Spring Security OAuth2,使指定客户端仅负责登录、第三方API客户端仅用于存储OAuth2AuthorizedClient?

如何配置Spring Security OAuth2,使指定客户端仅负责登录、第三方API客户端仅用于存储OAuth2AuthorizedClient?

我太懂你这种闹心的感觉了——本来只想用idp-client管登录,拿third-party-api去存第三方令牌,结果每次走第三方授权流程,登录态直接被替换成第三方的用户了,完全偏离预期对吧?

其实核心问题就是默认配置下,oauth2Login()会兜底处理所有授权码类型的客户端回调,包括你的第三方API客户端。当第三方授权完成回调时,Spring Security会默认把返回的用户信息当成新登录凭证,直接覆盖掉原来IDP的登录态。

下面给你一套实打实的配置方案,精准实现「idp-client专管登录,third-party-api只存令牌」的需求:

1. 调整SecurityFilterChain,给OAuth2Login设「专属白名单」

我们要明确告诉Spring Security:只有idp-client的请求属于登录流程,其他客户端的授权请求只走令牌存储逻辑,不碰登录态。

直接上可运行的配置代码:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .anyRequest().authenticated() // 所有请求都需要先登录
        )
        // 配置OAuth2登录:仅让idp-client处理登录逻辑
        .oauth2Login(oauth2 -> oauth2
            // 限制授权端点只响应idp-client的请求
            .authorizationEndpoint(authEndpoint -> authEndpoint
                .authorizationRequestResolver((request, clientRegistrationId) -> {
                    // 只有当客户端是idp-client时,才生成登录用的授权请求
                    if ("idp-client".equals(clientRegistrationId)) {
                        DefaultOAuth2AuthorizationRequestResolver resolver = 
                            new DefaultOAuth2AuthorizationRequestResolver(
                                clientRegistrationRepository(),
                                "/oauth2/authorization"
                            );
                        return resolver.resolve(request, clientRegistrationId);
                    }
                    return null; // 其他客户端不触发登录授权流程
                })
            )
            // 关键:只处理idp-client的回调请求
            .redirectionEndpoint(redir -> redir
                .baseUri("/login/oauth2/code/idp-client")
            )
        )
        // 配置OAuth2客户端:仅处理第三方API的授权,只存令牌不碰登录
        .oauth2Client(oauth2 -> oauth2
            .authorizedClientService(authorizedClientService())
        );
    return http.build();
}

// 授权客户端存储服务:默认存在内存,生产环境可换成数据库存储
@Bean
public OAuth2AuthorizedClientService authorizedClientService(
        ClientRegistrationRepository clientRegistrationRepository) {
    return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}

// 加载配置文件里的客户端注册信息(Spring通常会自动配置,这里手动写是为了明确)
@Bean
public ClientRegistrationRepository clientRegistrationRepository(
        Environment environment) {
    return new InMemoryClientRegistrationRepository(
        ClientRegistrations.fromIssuerLocation(environment.getProperty("spring.security.oauth2.client.provider.idp-client.issuer-uri"))
                .registrationId("idp-client")
                .clientId(environment.getProperty("spring.security.oauth2.client.registration.idp-client.client-id"))
                .clientSecret(environment.getProperty("spring.security.oauth2.client.registration.idp-client.client-secret"))
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .build(),
        ClientRegistrations.fromIssuerLocation(environment.getProperty("spring.security.oauth2.client.provider.third-party-api.issuer-uri"))
                .registrationId("third-party-api")
                .clientId(environment.getProperty("spring.security.oauth2.client.registration.third-party-api.client-id"))
                .clientSecret(environment.getProperty("spring.security.oauth2.client.registration.third-party-api.client-secret"))
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .build()
    );
}

2. 配置文件保持原样就行

你原来的application.yml配置完全没问题,Spring会自动加载两个客户端的注册信息:

spring:
  security:
    oauth2:
      client:
        registration:
          third-party-api:
            client-id: 你的第三方API客户端ID
            client-secret: 你的第三方API客户端密钥
            authorization-grant-type: authorization_code
          idp-client:
            client-id: 你的IDP客户端ID
            client-secret: 你的IDP客户端密钥
            authorization-grant-type: authorization_code

3. 核心逻辑拆解,让你明白为啥这么配

  • 锁死OAuth2Login的回调范围:通过redirectionEndpoint().baseUri("/login/oauth2/code/idp-client"),直接把登录回调的范围限定死在IDP客户端上,第三方API的回调(/login/oauth2/code/third-party-api)会自动交给oauth2Client()处理,不会触发登录逻辑。
  • OAuth2Client的专属职责oauth2Client()只会把第三方授权后的令牌、过期时间等信息存在OAuth2AuthorizedClientService里,完全不会碰当前的Security Context,所以原来IDP的登录态会稳稳保留。
  • 验证方法:登录IDP后,访问/oauth2/authorization/third-party-api完成授权,之后你可以通过@Autowired OAuth2AuthorizedClientService authorizedClientService,调用authorizedClientService.loadAuthorizedClient("third-party-api", 当前登录用户名)拿到第三方的授权信息,用里面的令牌调用API就行,此时登录用户还是IDP的那个用户。

额外给你提几个实用小建议

  1. 生产环境别用内存存储授权信息,把InMemoryOAuth2AuthorizedClientService换成JdbcOAuth2AuthorizedClientService,把令牌存在数据库里,避免会话失效后令牌丢失。
  2. 如果你觉得授权请求解析器的代码太啰嗦,也可以只保留redirectionEndpoint的配置,效果是一样的——只要回调不被oauth2Login()处理,就不会替换登录态。
  3. 完全不用碰Resource Server,你要的是Cookie会话登录,不是Bearer Token的资源服务器认证,别瞎引入相关依赖给自己添乱。

这样配置完,idp-client就安安稳稳管登录,third-party-api老老实实干令牌存储的活,再也不会出现登录态被替换的问题啦~

火山引擎 最新活动