为何OAuth2客户端角色未应用RoleHierarchy?需额外配置吗?
Great question! The default clientHasRole expression in Spring Security OAuth2 doesn't automatically apply role hierarchies—this is because client-level authority checks are handled separately from user-level ones, even if you've set up a RoleHierarchy bean.
Beyond setting the roleHierarchy on OAuth2MethodSecurityExpressionHandler, you'll need to customize the expression logic to make it respect the hierarchy for client roles. Here's how to do it step by step:
1. Create a custom OAuth2 security expression class
Extend OAuth2SecurityExpressionMethods and override the clientHasRole method to use your RoleHierarchy to resolve all reachable roles for the client:
public class CustomOAuth2SecurityExpressionMethods extends OAuth2SecurityExpressionMethods { private final RoleHierarchy roleHierarchy; public CustomOAuth2SecurityExpressionMethods(OAuth2Authentication auth, RoleHierarchy roleHierarchy) { super(auth); this.roleHierarchy = roleHierarchy; } @Override public boolean clientHasRole(String role) { // Get all authorities the client has, including inherited ones from the hierarchy Collection<? extends GrantedAuthority> reachableAuthorities = roleHierarchy.getReachableGrantedAuthorities( this.authentication.getOAuth2Request().getAuthorities() ); // Check if any of the reachable authorities match the target role return reachableAuthorities.stream() .anyMatch(auth -> auth.getAuthority().equals(role)); } }
2. Customize the OAuth2 method security expression handler
Override how the expression root is created in OAuth2MethodSecurityExpressionHandler to use your custom class:
@Bean public OAuth2MethodSecurityExpressionHandler oAuth2MethodSecurityExpressionHandler(RoleHierarchy roleHierarchy) { return new OAuth2MethodSecurityExpressionHandler() { @Override protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication auth, MethodInvocation invocation) { CustomOAuth2SecurityExpressionMethods root = new CustomOAuth2SecurityExpressionMethods( (OAuth2Authentication) auth, roleHierarchy ); // Copy over default handlers (permission evaluator, trust resolver) root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(getTrustResolver()); root.setRoleHierarchy(roleHierarchy); return root; } }; }
3. Wire the custom handler into global method security
Make sure your global method security configuration uses this custom handler:
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { private final OAuth2MethodSecurityExpressionHandler oAuth2ExpressionHandler; public MethodSecurityConfig(OAuth2MethodSecurityExpressionHandler oAuth2ExpressionHandler) { this.oAuth2ExpressionHandler = oAuth2ExpressionHandler; } @Override protected MethodSecurityExpressionHandler createExpressionHandler() { return oAuth2ExpressionHandler; } }
Key Notes
- Double-check that your
RoleHierarchybean is correctly configured (e.g., usingRoleHierarchyImplwith the proper hierarchy string like"ROLE_CLIENT_ADMIN > ROLE_CLIENT_USER"). - Ensure the role strings you pass to
clientHasRolematch exactly what's configured in your hierarchy (including anyROLE_prefix if you're using default role naming).
This setup will make @PreAuthorize("#oauth2.clientHasRole('somerole')") respect your role hierarchy, checking not just for the exact role but also any higher-level roles that include it via the hierarchy.
内容的提问来源于stack exchange,提问作者Laures

