Spring Security OAuth2授权服务器升级后无法从RegisteredClient获取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
相关产品推荐
相关产品推荐

