如何在Spring Boot中结合KeyCloak实现RBAC与ABAC混合授权?
基于KeyCloak的RBAC+ABAC混合权限控制方案(Spring Boot)
核心思路
利用KeyCloak生成的JWT中携带的角色信息(RBAC基础)和自定义用户属性(ABAC动态判断),结合Spring Security的表达式语言实现混合权限控制,无需引入额外复杂框架。
步骤1:KeyCloak配置
在KeyCloak中完成以下配置,确保JWT包含所需的RBAC和ABAC信息:
- RBAC角色配置:在Realm中创建角色(如
ROOT、STAFF),分配给对应用户/组。 - ABAC自定义属性配置:
- 进入用户详情页,添加自定义属性(如
department: IT、allowed_endpoints: "/extra/it-only,/extra/custom"、department_manager: true)。 - 在客户端的
Mappers中添加"User Attribute"类型映射,将自定义属性关联到JWT的claim中(例如设置Token Claim Name为custom_claims.department,对应User Attribute为department)。
- 进入用户详情页,添加自定义属性(如
步骤2:Spring Security基础配置(基于spring-addons-webmvc-jwt-resource-server)
@Configuration @EnableWebSecurity @EnableMethodSecurity public class WebSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth // 纯RBAC规则:管理员专属端点 .requestMatchers("/admin/**").hasAuthority("ROOT") // 纯RBAC规则:普通员工基础端点 .requestMatchers("/staff/**").hasAuthority("STAFF") // 混合规则:员工角色 + (部门经理权限 或 端点在允许列表中) .requestMatchers("/extra/**").access("hasAuthority('STAFF') and (hasClaim('custom_claims.department_manager', 'true') or contains(claim('custom_claims.allowed_endpoints'), request.servletPath))") .anyRequest().authenticated() ); // 启用JWT资源服务器,自动处理KeyCloak JWT解析 return http.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))).build(); } // 自定义JWT转换器:将KeyCloak角色和自定义权限转为Spring Security Authorities @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { var jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); jwtGrantedAuthoritiesConverter.setAuthorityPrefix(""); // 移除默认的SCOPE_前缀,直接使用KeyCloak角色名 var converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(jwt -> { var authorities = jwtGrantedAuthoritiesConverter.convert(jwt); // 将自定义权限列表转为Authorities(可选,方便RBAC式调用) var customPermissions = jwt.getClaimAsStringList("custom_claims.permissions"); if (customPermissions != null) { authorities.addAll(customPermissions.stream().map(SimpleGrantedAuthority::new).toList()); } return authorities; }); return converter; } }
步骤3:控制器层面的混合权限控制示例
@RestController public class DemoController { // 纯RBAC:仅ROOT角色可访问 @GetMapping("/admin/dashboard") @PreAuthorize("hasAuthority('ROOT')") public MessageDto adminDashboard() { return new MessageDto("Admin Dashboard"); } // 纯RBAC:仅STAFF角色可访问 @GetMapping("/staff/profile") @PreAuthorize("hasAuthority('STAFF')") public MessageDto staffProfile() { return new MessageDto("Staff Profile"); } // 混合控制:STAFF角色 + (IT部门 或 拥有CAN_ACCESS_IT_EXTRA权限) @GetMapping("/extra/it-only") @PreAuthorize("hasAuthority('STAFF') and (hasClaim('custom_claims.department', 'IT') or hasAuthority('CAN_ACCESS_IT_EXTRA'))") public MessageDto itExclusiveContent() { return new MessageDto("IT Department Exclusive Content"); } // 混合控制:STAFF角色 + 当前端点在用户允许列表中 @GetMapping("/extra/custom") @PreAuthorize("hasAuthority('STAFF') and contains(claim('custom_claims.allowed_endpoints'), request.servletPath)") public MessageDto customAllowedContent() { return new MessageDto("Custom Allowed Content"); } }
备选方案:分层拆分角色(简化版)
如果觉得ABAC表达式过于灵活复杂,可将细粒度权限拆分为子角色:
- 创建子角色
STAFF_IT、STAFF_FINANCE、STAFF_WITH_EXTRA - 在KeyCloak中给用户分配主角色
STAFF+ 对应子角色 - 权限控制直接使用RBAC表达式:
@GetMapping("/extra/it-only") @PreAuthorize("hasAnyAuthority('STAFF_IT', 'ROOT')") public MessageDto itExclusiveContent() { return new MessageDto("IT Department Exclusive Content"); }
这种方案更直观,适合权限规则相对固定的场景。
关键注意事项
- 用JWT调试工具(如jwt.io)验证KeyCloak生成的JWT是否包含自定义claim。
- Spring Security表达式中,
claim()可直接获取JWT字段,contains()用于判断列表是否包含指定值,更多表达式函数可参考Spring Security官方文档。 - 若需更复杂的ABAC逻辑,可自定义
SecurityExpressionRoot扩展表达式方法。
内容的提问来源于stack exchange,提问作者J. M. Arnold
相关产品推荐
相关产品推荐

