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

如何通过访问令牌响应设置Spring Security中OAuth2AuthorizedClient的主体名称?

如何通过访问令牌响应设置Spring Security中OAuth2AuthorizedClient的主体名称?

嘿,我来帮你解决这个问题!你现在用的是Spring Boot 3搭配Spring Security 6的OAuth2客户端,用的是authorization_code授权码模式,想要从访问令牌的响应里自定义设置OAuth2AuthorizedClient的主体名称对吧?

默认情况下,Spring Security要么从ID Token的sub字段提取主体名称,要么会调用用户信息端点去获取。但如果你的授权服务器把主体信息直接放在access token的响应体里(比如某个自定义字段),那我们就得自己写点逻辑来搞定这个事。

下面是具体的实现步骤,结合你的现有配置来改:

第一步:自定义令牌响应转换器

我们需要先写一个转换器,从授权服务器返回的token响应里提取出我们要的主体字段,把它存到access token的额外参数里方便后续使用:

public class CustomAccessTokenResponseConverter implements OAuth2AccessTokenResponseConverter {
    // 用默认转换器处理基础的token响应
    private final OAuth2AccessTokenResponseConverter defaultConverter = new DefaultOAuth2AccessTokenResponseConverter();

    @Override
    public OAuth2AccessTokenResponse convert(Map<String, Object> tokenResponseParameters) {
        OAuth2AccessTokenResponse response = defaultConverter.convert(tokenResponseParameters);
        // 这里替换成你实际令牌响应里的主体字段名,比如user_id、username或者sub
        String principalName = (String) tokenResponseParameters.get("user_id");
        // 把主体名称存到access token的额外参数中
        return OAuth2AccessTokenResponse.withResponse(response)
                .additionalParameters(Map.of("custom_principal", principalName))
                .build();
    }
}

第二步:修改Security配置,绑定自定义逻辑

接下来我们要把上面的转换器集成到你的Security配置里,同时自定义OAuth2AuthorizedClientManager来设置主体名称:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 初始化自定义的token响应客户端
        DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
        tokenResponseClient.setAccessTokenResponseConverter(new CustomAccessTokenResponseConverter());

        http.authorizeHttpRequests((authorize) -> authorize
                        .anyRequest().authenticated()
                ).oauth2Client(oauth2 -> oauth2
                        .authorizationCodeGrant(codeGrant -> codeGrant
                                .accessTokenResponseClient(tokenResponseClient)))
                .formLogin(Customizer.withDefaults());
        return http.build();
    }

    // 自定义OAuth2AuthorizedClientManager,用来设置自定义主体名称
    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService authorizedClientService) {

        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);

        // 替换默认的授权客户端提供者,加入自定义逻辑
        authorizedClientManager.setAuthorizedClientProvider(customAuthorizedClientProvider());

        return authorizedClientManager;
    }

    private OAuth2AuthorizedClientProvider customAuthorizedClientProvider() {
        // 构建默认的授权码模式提供者
        DefaultAuthorizationCodeOAuth2AuthorizedClientProvider codeProvider =
                (DefaultAuthorizationCodeOAuth2AuthorizedClientProvider)
                        OAuth2AuthorizedClientProviderBuilder.builder().authorizationCode().build().getAuthorizationCode();

        // 包装提供者,自定义授权客户端的构建逻辑
        return new OAuth2AuthorizedClientProvider() {
            @Override
            public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
                OAuth2AuthorizedClient authorizedClient = codeProvider.authorize(context);
                if (authorizedClient != null) {
                    // 从access token的额外参数里取出之前存的主体名称
                    String customPrincipal = (String) authorizedClient.getAccessToken().getAdditionalParameters().get("custom_principal");
                    // 重新构建OAuth2AuthorizedClient,替换默认的主体名称
                    return new OAuth2AuthorizedClient(
                            authorizedClient.getClientRegistration(),
                            customPrincipal,
                            authorizedClient.getAccessToken(),
                            authorizedClient.getRefreshToken());
                }
                return null;
            }
        };
    }
}

小提示

你只需要把代码里的user_id替换成你实际令牌响应中对应的主体字段名就行,比如如果你的响应里主体字段是username或者sub,直接改tokenResponseParameters.get("user_id")里的参数名就可以了。

备注:内容来源于stack exchange,提问作者AwesomeDude091

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.16 09:43:06