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

Spring Security OAuth2授权服务器升级后无法从RegisteredClient获取Scope

Spring Security OAuth2 Authorization Server v1.1 客户端凭证模式Scope问题解决

变更原因

从v0.3.x版本开始,Spring Security OAuth2 Authorization Server调整了Client Credentials模式下的Scope处理逻辑,核心原因是对齐OAuth 2.0规范(RFC 6749)。规范要求该模式下的Scope必须由客户端在请求中明确传递,除非授权服务器为客户端配置了默认Scope。早期版本直接从RegisteredClient读取Scope的逻辑属于非规范实现,后续版本做了修正,确保行为符合标准。

修复方案

方案1:配置客户端默认Scope(推荐)

如果业务不需要客户端每次请求传递Scope,可以为客户端配置默认Scope,授权服务器会自动使用这些Scope生成令牌。

代码示例(内存客户端)

@Bean
public RegisteredClientRepository registeredClientRepository() {
    RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("your-client-id")
            .clientSecret("{bcrypt}your-encoded-secret")
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            // 配置客户端默认Scope
            .scopes(scopes -> scopes.addAll("read:data", "write:data"))
            .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
            .build();
    return new InMemoryRegisteredClientRepository(client);
}

如果是从数据库加载RegisteredClient,确保数据库中客户端记录的Scope字段已正确存储,且RegisteredClientRepository实现能正确加载这些Scope。

方案2:自定义认证Provider

若必须从自定义数据库表读取Scope(而非依赖RegisteredClient),可以自定义OAuth2ClientCredentialsAuthenticationProvider,解决注入问题的关键是通过构造函数注入所需依赖。

自定义Provider代码

@Component
public class CustomClientCredentialsAuthProvider extends OAuth2ClientCredentialsAuthenticationProvider {

    private final OAuth2AuthorizationService authorizationService;
    private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;

    // 构造函数注入依赖,避免手动获取上下文
    public CustomClientCredentialsAuthProvider(RegisteredClientRepository registeredClientRepo,
                                              OAuth2AuthorizationService authorizationService,
                                              OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
        super(registeredClientRepo);
        this.authorizationService = authorizationService;
        this.tokenGenerator = tokenGenerator;
        super.setTokenGenerator(tokenGenerator);
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        OAuth2ClientCredentialsAuthenticationToken authToken =
                (OAuth2ClientCredentialsAuthenticationToken) authentication;

        // 先执行父类的客户端身份验证逻辑
        OAuth2ClientCredentialsAuthenticationToken authenticated =
                (OAuth2ClientCredentialsAuthenticationToken) super.authenticate(authentication);

        // 从自定义数据库表读取Scope(替换为你的实际查询逻辑)
        Set<String> dbScopes = getScopesFromDatabase(authToken.getClientId());

        // 构建包含自定义Scope的认证结果
        OAuth2ClientCredentialsAuthenticationToken result = new OAuth2ClientCredentialsAuthenticationToken(
                authenticated.getPrincipal(),
                authenticated.getCredentials(),
                dbScopes
        );
        result.setDetails(authenticated.getDetails());

        // 生成并保存授权信息
        OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(authenticated.getRegisteredClient())
                .principalName(authenticated.getName())
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .token(OAuth2TokenType.ACCESS_TOKEN, tokenGenerator.generate(result))
                .build();
        authorizationService.save(authorization);

        return result;
    }

    // 自定义数据库查询方法
    private Set<String> getScopesFromDatabase(String clientId) {
        // 示例:从client_scope表查询该客户端的所有Scope
        return Set.of("custom:read", "custom:write");
    }
}

替换默认Provider

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig {

    @Bean
    public OAuth2AuthorizationServerConfiguration authServerConfig(
            CustomClientCredentialsAuthProvider customProvider) {
        return OAuth2AuthorizationServerConfiguration.builder()
                .clientCredentialsAuthenticationProvider(customProvider)
                .build();
    }
}

方案3:令牌自定义扩展

如果仅需要在生成令牌时添加自定义Scope,无需替换整个Provider,可使用OAuth2TokenCustomizer:

@Bean
public OAuth2TokenCustomizer<JwtEncodingContext> jwtScopeCustomizer() {
    return context -> {
        if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType())) {
            String clientId = context.getRegisteredClient().getId();
            Set<String> dbScopes = getScopesFromDatabase(clientId);
            // 更新JWT的scope声明
            context.getClaims().claim("scope", String.join(" ", dbScopes));
            // 更新令牌的Scope属性
            context.getToken().getClaims().put("scope", dbScopes);
        }
    };
}

注意事项

  • 确保自定义Scope符合授权服务器的配置,避免出现无效Scope的验证错误。
  • 若使用JWT令牌,Scope声明需符合规范(空格分隔的字符串或数组格式)。

内容的提问来源于stack exchange,提问作者wsyours

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:23:08