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

如何在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自定义属性配置:
    1. 进入用户详情页,添加自定义属性(如department: IT、allowed_endpoints: "/extra/it-only,/extra/custom"、department_manager: true)。
    2. 在客户端的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:10:40