如何将自定义授权服务集成到Spring Authorization Server并实现权限范围管控?
实现方案与说明
一、流程合理性确认
你的流程逻辑完全通顺:认证通过后提取用户组→结合请求scope校验权限→展示可授权的scope→生成包含用户同意scope的令牌。需要注意两个细节:
openid是OIDC协议的标识,用于触发ID Token生成,本身不对应资源权限,建议在自研授权服务校验时排除该scope;profile属于OIDC标准用户信息scope,通常只要用户认证通过即可默认允许,若需纳入自研权限控制,可同步加入校验逻辑。
二、具体实现步骤
1. 把用户组存入Authentication对象
在你已实现的AuthenticationProvider中,认证成功后将用户组信息存入自定义UserDetails,方便后续流程获取:
public class CustomAuthenticationProvider implements AuthenticationProvider { private final UserManagementService userManagementService; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 调用自研用户管理服务完成认证 UserInfo userInfo = userManagementService.authenticate( authentication.getName(), authentication.getCredentials().toString() ); // 自定义UserDetails,包含groups字段 CustomUserDetails userDetails = new CustomUserDetails( userInfo.getUsername(), "", AuthorityUtils.createAuthorityList("ROLE_USER") ); userDetails.setGroups(userInfo.getGroups()); return new UsernamePasswordAuthenticationToken( userDetails, authentication.getCredentials(), userDetails.getAuthorities() ); } // 重写supports方法 @Override public boolean supports(Class<?> authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } } // 自定义UserDetails实现类 public class CustomUserDetails implements UserDetails { private List<String> groups; // 构造方法、getter/setter // 实现UserDetails接口的其他方法(如isAccountNonExpired等,按需返回true/false) }
2. 拦截授权请求,获取允许的scope
通过实现OAuth2AuthorizationRequestCustomizer,在用户认证后介入授权请求流程,调用自研授权服务过滤出用户可访问的scope:
@Component public class ScopeFilterCustomizer implements OAuth2AuthorizationRequestCustomizer<OAuth2AuthorizationRequest.Builder> { private final AuthorizationServiceClient authorizationServiceClient; @Override public void customize(OAuth2AuthorizationRequest.Builder builder, Principal principal) { // 从Principal中提取用户组 CustomUserDetails userDetails = (CustomUserDetails) ((UsernamePasswordAuthenticationToken) principal).getPrincipal(); List<String> groups = userDetails.getGroups(); // 提取请求中的业务scope,排除openid Set<String> requestedScopes = builder.build().getScopes(); List<String> businessScopes = requestedScopes.stream() .filter(scope -> !"openid".equals(scope)) .collect(Collectors.toList()); // 调用自研授权服务,获取允许的scope AllowScopeResponse allowResponse = authorizationServiceClient.getAllowedScopes(groups, businessScopes); List<String> allowedScopes = allowResponse.getAllowedScopes(); // 重新组装最终scope:允许的业务scope + 保留原请求的openid(如果有) Set<String> finalScopes = new HashSet<>(allowedScopes); if (requestedScopes.contains("openid")) { finalScopes.add("openid"); } // 更新授权请求的scope builder.scopes(finalScopes); } }
然后在Spring Security配置中注册该Customizer:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public OAuth2AuthorizationRequestResolver authorizationRequestResolver( ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizationRequestCustomizer<OAuth2AuthorizationRequest.Builder> customizer) { DefaultOAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver( clientRegistrationRepository, "/oauth2/authorize" ); resolver.setAuthorizationRequestCustomizer(customizer); return resolver; } // 其他配置(如AuthenticationProvider注册、csrf配置等) }
3. 自定义授权同意页面
Spring Authorization Server默认会展示授权请求中的scope,我们只需替换为自定义页面,展示过滤后的允许scope即可:
- 创建Thymeleaf模板
consent.html:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>授权确认</title> </head> <body> <h1>授权确认</h1> <p>客户端 <span th:text="${clientId}"></span> 请求访问以下权限:</p> <ul> <li th:each="scope : ${scopes}" th:text="${scope}"></li> </ul> <form method="post" th:action="${consentUri}"> <input type="hidden" name="client_id" th:value="${clientId}"> <input type="hidden" name="state" th:value="${state}"> <button type="submit" name="action" value="approve">同意</button> <button type="submit" name="action" value="deny">拒绝</button> </form> </body> </html>
- 在配置中指定自定义页面:
@Bean public OAuth2AuthorizationConsentController authorizationConsentController( OAuth2AuthorizationConsentService authorizationConsentService, OAuth2AuthorizationRequestResolver authorizationRequestResolver) { OAuth2AuthorizationConsentController controller = new OAuth2AuthorizationConsentController(authorizationConsentService); controller.setAuthorizationRequestResolver(authorizationRequestResolver); controller.setConsentPage("consent"); // 指定模板名称 return controller; }
4. 生成包含同意scope的令牌
Spring Authorization Server默认会根据用户同意的scope生成令牌,若需自定义令牌内容(如把scope写入claims),可实现OAuth2TokenCustomizer:
@Component public class JwtTokenCustomizer implements OAuth2TokenCustomizer<JwtEncodingContext> { @Override public void customize(JwtEncodingContext context) { Set<String> consentedScopes = context.getAuthorization().getAuthorizedScopes(); // 将同意的scope存入令牌claims(默认已包含,可按需扩展) context.getClaims().claim("authorized_scopes", consentedScopes); } }
在授权服务器配置中注册该Customizer:
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig { @Bean public OAuth2TokenGenerator<?> tokenGenerator( JwtEncoder jwtEncoder, OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer) { JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder); jwtGenerator.setJwtCustomizer(tokenCustomizer); return new DelegatingOAuth2TokenGenerator( jwtGenerator, new OAuth2AccessTokenGenerator(), new OAuth2RefreshTokenGenerator() ); } // 其他配置(如ClientRegistrationRepository、JwtEncoder等) }
三、关键逻辑纠正
- 不要将
openid传入自研授权服务做权限校验,它是OIDC协议的标识,不对应资源权限; - 若
profile需纳入权限控制,需在自研授权服务中添加对应规则,否则可默认允许客户端的该scope请求。
内容的提问来源于stack exchange,提问作者Suvin Nimnaka Sukka
相关产品推荐
相关产品推荐

