Keycloak IAM 细粒度权限与授权策略实战 — Groups vs Roles、Authorization Services | IDaaS Book
场景
你已经在 Keycloak 里配好了 Realm、Client 和用户,SSO 登录也跑通了。现在业务方提了三个需求:
- “这个 API 只有管理员能调,普通用户不行”
- “同一个 API,部门 A 的人能写、部门 B 的人只能读”
- “用户可以查看自己的订单,但不能看别人的”
你在 Keycloak 的 Groups、Roles、Composite Roles、Authorization Services 之间绕晕了——到底该用哪个?配了半天 Policy Evaluation 还是不通过。
适用与不适用
| 适用 | 不适用 |
|---|---|
| 需要对单个资源做细粒度控制(按 HTTP 方法、URI、资源属性) | 只需要简单的「已登录/未登录」判断 |
| 多个应用共用 Keycloak,授权规则各不相同 | 授权规则完全由应用内部代码管理 |
| 需要 ABAC/ReBAC 混合模型 | 只需要 OAuth 2.0 Scope 就够了 |
| 有管理后台需要按角色区分菜单可见性 | 你的应用根本不区分角色 |
还有一个边界值得先确认:Authorization Services 面向的是 HTTP 风格的资源服务器(策略端点 + UMA 语义)。非 HTTP 协议、或自带权限引擎的系统不会来调 Keycloak 的策略端点——例如 Kafka 的 SASL/OAUTHBEARER 只把 token 变成 principal,权限走 ACL,IdP 在那类系统里只负责认证,见 Kafka 接入 Keycloak OAUTHBEARER;同理 MinIO 的 policy 映射 也是资源侧自决。这两类系统里的「授权」不要往 Keycloak 的 Authorization Services 上靠。
还有一种介于两者之间的形态,容易被归错类:
RabbitMQ 的 IAM 权限映射并不调用 Keycloak 的策略端点,但它的权限模型本身也不是独立的一套——rabbitmq_auth_backend_oauth2 直接把 token 的 scope 翻译成 vhost 级的 configure / read / write。也就是说授权数据仍然来自 IdP 签发的 token,只是翻译规则写在 RabbitMQ 侧(scope_prefix、scope_aliases)。判断一个系统属于哪一类,看它是否需要向 Keycloak 发请求:需要就是 Authorization Services 的适用场景,不需要则看权限是「从 token 里读」还是「资源侧另有数据库」。
核心概念速览
在深入配置之前,先理清几个最容易混淆的概念:
Group vs Role
| Group | Role | |
|---|---|---|
| 本质 | 用户的组织归属 | 用户的权限标签 |
| 典型值 | engineering、sales、cn-team | admin、viewer、order-manager |
| 能嵌套吗 | ✅ 支持子 Group | ✅ 通过 Composite Role 嵌套 |
| 适合干什么 | 按部门/团队分组、LDAP 映射 | 授予具体权限、做 RBAC |
| 不适合干什么 | 直接做权限判断 | 表达部门层级关系 |
简单原则:Group 说「你是谁的人」,Role 说「你能干什么」。一个用户可以有多个 Group 和多个 Role。
Realm Role vs Client Role
| Realm Role | Client Role | |
|---|---|---|
| 作用域 | 整个 Realm 通用 | 仅某个 Client 可见 |
| 典型场景 | global-admin、auditor | app-a:editor、app-b:viewer |
| 使用建议 | 跨应用共享的角色 | 每个应用自己的角色 |
实践建议:默认用 Client Role,只有真正跨应用的角色才用 Realm Role。这样每个应用的角色命名空间独立,不会互相污染。
Composite Role(组合角色)
Composite Role 就是一个角色「包含」其他角色。比如:
order-admin是一个 Composite Role- 它包含
order-viewer+order-editor+order-exporter - 把
order-admin赋给用户,自动拥有三个子角色的全部权限
这比给每个用户逐个赋三个角色更清爽,也方便统一调整。
方案一:基于 Roles 的简单 RBAC
Keycloak 端配置
1. 创建 Client Roles
在目标 Client → Roles 下创建:
app-admin
app-user
app-viewer2. 创建 Composite Role
创建 app-admin 时勾选 Composite,添加关联角色 app-user、app-viewer。
3. 给用户分配角色
Users → 目标用户 → Role Mapping → Assign Role → 选择对应的 Client Role。
应用端读取角色
应用的 JWT Token(Access Token)中会包含 realm_access.roles 和 resource_access.<client-id>.roles:
{
"realm_access": {
"roles": ["default-roles-myapp", "offline_access", "uma_authorization"]
},
"resource_access": {
"my-app": {
"roles": ["app-admin"]
}
}
}应用代码据此做权限判断(以 Spring Security 为例):
@PreAuthorize("hasRole('app-admin')")
@GetMapping("/admin/users")
public List<User> listUsers() { ... }
@PreAuthorize("hasAnyRole('app-admin', 'app-user')")
@GetMapping("/api/orders")
public List<Order> listOrders() { ... }验证
# 获取 Token
TOKEN=$(curl -s -X POST "https://keycloak.example.com/realms/myapp/protocol/openid-connect/token" \
-d "client_id=my-app" \
-d "username=testuser" \
-d "password=testpass" \
-d "grant_type=password" | jq -r '.access_token')
# 解码 Token 看 roles
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq '.resource_access."my-app".roles'这个方案够用吗?
- ✅ 大多数中小型应用的 RBAC 需求
- ✅ 配置简单,应用端适配成本低
- ❌ 做不到「同一个 API 按 HTTP 方法区分权限」
- ❌ 做不到「部门 A 看自己的数据,部门 B 看自己的」
当你需要资源级别的权限控制时,就需要 Authorization Services。
方案二:Keycloak Authorization Services(细粒度授权)
Keycloak Authorization Services 基于 UMA 2.0 和 OAuth 2.0,核心模型是:
Resource(资源) → Scope(操作)→ Policy(策略)→ Permission(权限)- Resource:你要保护的东西(如
/api/orders、Order:123) - Scope:对资源的操作(如
view、edit、delete) - Policy:谁可以操作(角色策略、用户策略、JS 策略、Group 策略等)
- Permission:把 Resource + Scope + Policy 绑在一起
最小可运行配置
Step 1: 启用 Authorization Services
Client → Settings → Authorization Enabled → ON
启用后,Client 页面会出现 Authorization 标签页。
Step 2: 创建 Resource
Authorization → Resources → Create resource:
| 字段 | 值 | 说明 |
|---|---|---|
| Name | Orders API | 资源名称 |
| Display name | 订单管理 API | 显示名称 |
| URIs | /api/orders/* | 匹配的 URI 模式 |
| Scopes | view, create, edit, delete | 该资源支持的操作 |
Step 3: 创建 Policies
Authorization → Policies → Create policy → Role:
Policy 1: 管理员完全访问
| 字段 | 值 |
|---|---|
| Name | Admin Full Access |
| Roles | app-admin |
| Logic | Positive |
Policy 2: 普通用户只读
| 字段 | 值 |
|---|---|
| Name | User Read Only |
| Roles | app-user |
| Logic | Positive |
Step 4: 创建 Permissions
Authorization → Permissions → Create permission → Scope-Based:
Permission 1: 管理员全部操作
| 字段 | 值 |
|---|---|
| Name | Admin can manage orders |
| Resources | Orders API |
| Scopes | view, create, edit, delete |
| Policies | Admin Full Access |
| Decision Strategy | Affirmative |
Permission 2: 普通用户只能查看
| 字段 | 值 |
|---|---|
| Name | User can view orders |
| Resources | Orders API |
| Scopes | view |
| Policies | User Read Only |
| Decision Strategy | Affirmative |
Step 5: 测试 Policy Evaluation
Authorization → Evaluate:
- 输入测试用户
- 选择 Resource:
Orders API - 选择 Scope:
edit - 如果用户是
app-admin→ PERMIT - 如果用户是
app-user→ DENY
应用端集成
应用端有两种集成方式:
方式 A:请求方令牌(Bearer Token,主动授权检查)
# 获取 RPT(Requesting Party Token)
curl -X POST "https://keycloak.example.com/realms/myapp/protocol/openid-connect/token" \
-d "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" \
-d "audience=my-app" \
-d "permission=Orders API#view" \
-H "Authorization: Bearer $USER_TOKEN"返回的 RPT 中包含该用户对 Orders API 的 view 权限。
方式 B:应用端本地检查(策略执行点模式)
应用通过 Keycloak 的 Token Introspection 或本地解析 Access Token 判断权限:
// Spring Security + Keycloak Adapter 模式
@GetMapping("/api/orders")
public List<Order> listOrders() {
// 应用自行检查 JWT 中的 roles/scopes
}方式 C:Keycloak Policy Enforcer(推荐)
在 keycloak.json 或 application.properties 中配置:
keycloak.policy-enforcer-config.enforcement-mode=ENFORCING
keycloak.policy-enforcer-config.paths[0].path=/api/orders/*
keycloak.policy-enforcer-config.paths[0].methods[0].method=GET
keycloak.policy-enforcer-config.paths[0].methods[0].scopes=view应用不写权限代码,所有授权判断由 Keycloak Adapter 在请求入口完成。
方案三:使用 Groups 做部门级数据隔离
当授权不只是「能不能访问 API」而是「能看到哪些数据」时,Groups 更合适。组还有第二种常见用途:驱动下游应用内部角色——应用拿 groups claim 自己算角色,而不是每个应用都在 Keycloak 里单独建一套角色。以 Grafana 为例,组到 Admin/Editor/Viewer 的映射配置与踩坑见
Grafana 接入 Keycloak OIDC。
场景
- 浙江分公司的员工只能查看浙江的订单
- 上海分公司的员工只能查看上海的订单
配置
- 创建 Groups:
/china/zhejiang、/china/shanghai - 创建 Group-based Policy:
Zhejiang Users(绑定/china/zhejiangGroup) - 创建 Resource:
Orders-Zhejiang(URI:/api/orders?region=zhejiang) - Permission:
Zhejiang Users→Orders-Zhejiang→view
应用端实现
应用的 /api/orders 接受 region 查询参数,根据当前用户的 Groups 做过滤:
@GetMapping("/api/orders")
public List<Order> listOrders(Authentication auth) {
Set<String> groups = auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.filter(g -> g.startsWith("ROLE_GROUP_"))
.collect(Collectors.toSet());
if (groups.contains("/china/zhejiang")) {
return orderService.findByRegion("zhejiang");
}
// ...
}注意:Keycloak 默认不会把 Groups 放进 JWT。需要添加 Group Mapper(Client → Client Scopes → 对应的 scope → Mappers → Add mapper → Group Membership),勾选 “Full group path”。
方案三的边界:组织组不能用于授权策略
如果租户是用 Keycloak Organizations 建的,要特别注意组的作用域:组织组(Organization Groups)不能用于 Keycloak authorization policies,Group-based Policy 只能选 realm group。这不是版本缺陷,而是有意的设计边界——authorization policy 在 Realm 级别评估,组织组在组织上下文里有独立的路径命名空间,两者不在同一个坐标系里。
结果是:用 Organizations 做 B2B 多租户时,如果授权决策要在 Keycloak 内部完成,仍然需要把参与策略的组建在 Realm 级;组织组只适合承载租户内的组织结构和(26.7 起的)角色继承,并把结果通过 organization claim 下发给应用,由应用侧做判断。组织组与 realm group 的能力差异见
Keycloak Organizations 多租户实践。
常见错误
| 症状 | 原因 | 解决 |
|---|---|---|
| Policy Evaluation 始终 DENY | 用户没有 Policy 要求的 Role/Group | 检查用户 Role Mapping,确认角色已分配 |
| RPT 请求返回 403 | Client 未启用 Authorization Services | Client → Settings → Authorization Enabled |
| JWT 中没有 Groups | 缺少 Group Mapper | 添加 Group Membership Mapper |
| 改了策略不生效 | Authorization 缓存 | 清除 Keycloak 缓存或重启;生产环境设短 TTL |
resource_access 为空 | Client 没有定义 Client Roles | 先在 Client → Roles 创建角色 |
| Composite Role 的子角色不生效 | 子角色分配到了 Realm Role 而非同一 Client | Composite Role 的子角色必须在同一 Client Scope 内 |
升级到 Keycloak 26.7.3 前要回归的 IAM 管理面行为
26.7.3 的 45 项已解决条目中有 10 项标注 admin/fine-grained-permissions,另有 1 项标注 admin/rbac——这一版改动的重心就是 FGAP v2 的授权判定本身。修复覆盖读、写和评估语义三类:通过 POST /users 创建用户时可以选择未被授权的组(CVE-2026-18571);GET /roles/{role}/users 在缺少 per-user view filter 时返回用户 PII(CVE-2026-17059);删除 role-composite 时委派管理员可越权移除特权子角色(CVE-2026-16106);聚合策略的 partial evaluation 与运行时语义不一致(#51143)、漏掉 extendChildren 的祖先组策略(#51144)。
对用 FGAP v2 做委派管理的部署,升级不等于换镜像:用一个只被授予部分权限的管理员账号,把实际授予它的操作重新走一遍(建用户、改组、改 client、查 role 用户列表),确认管理界面上的「有效权限预览」与真实判定一致。分组解读与验证清单见 Keycloak 26.7.3 安全补丁解读。
还有一条会直接影响自动化流水线的边界:Keycloak 在 FGAP V2 下有意屏蔽 admin-permissions 客户端的 Authorization Services API 端点,外部调用得到的是 HTTP 400 unknown_error(见 keycloak/keycloak#43977)。这意味着用 keycloak-config-cli 这类声明式工具时,该客户端的授权模型不在可管理范围内——不要试图绕过,也不要把它写进配置文件后指望 apply 生效。工具侧的这条限制记录在
IAM 配置即代码:keycloak-config-cli 声明式 Realm 管理与误删防护。
26.8.0 起的三条授权语义变化
这三条不改配置项名字,但会改变判定结果或令牌内容,属于「升级后行为不一样了」的类型:
1. 组策略的裸组名只匹配顶层组(CVE-2026-19608)。 修复前,token 里一个裸名 Admins 会匹配任意层级的同名组;修复后只解析为顶层 realm 组。利用面很清楚:某个用户只要属于任意一个叫 Admins 的嵌套组,就能满足指向另一个同名组的策略。
判断自己是否受影响,看 Group Membership protocol mapper 的 Full group path 选项——默认是开的,token 里是 /Organization/Admins 这类完整路径,不受影响;只有显式关掉它、同时又用组策略指向嵌套组的部署需要处理。处理方式是打开 Full group path 让 token 输出完整路径(沿用本站
Keycloak + Argo CD OIDC 单点登录 里讨论过的取舍:关掉该选项会让同名层级组退化为歧义)。另外,含斜杠的组名现在优先按顶层组名解析,再考虑路径解释——顶层组名与嵌套组路径重名时,顶层优先。
2. 资源 URI 匹配改为先归一化。 Authorization Services 在把请求 URI 与配置的 resource URI 比较前,现在会去掉矩阵参数(如 ;jsessionid=...,含百分号编码形式)、解析点段(/foo/../admin)、解码 %2F、合并重复斜杠、去掉尾斜杠,并丢弃 query 与 fragment。这堵住了「用变形 URI 绕开受保护资源、落到更宽松的 /* 规则」的攻击路径;代价是如果你的 resource 定义本来就在区分这些形式,升级后它们会被视为等价,必须复核。
3. Full Scope Allowed 被弃用,令牌签发时会告警。 开启该开关的客户端,其 access token 会带上用户在所有 client 和 realm 上的角色,与本页讨论的「显式 role scope mapping」方向相反。26.8.0 起服务端在每次签发令牌时记 WARN(内置管理客户端被抑制),后续版本会移除该开关。存量清点命令、迁移顺序与用 client policy 的 full-scope-disabled executor 自动收敛的做法,见
Keycloak 26.8.0 升级:IAM 破坏性变更排查。
回滚方式
- 角色分配回滚:Users → Role Mapping → 移除错误角色重新分配
- Authorization Services 回滚:Client → Settings → Authorization Enabled → OFF(所有策略和权限配置保留在数据库但不再生效)
- Policy 回滚:删除有问题的 Policy 或 Permission,重建
- 生产环境注意:Authorization Services 的配置变更会立即生效,建议先在 Staging 环境验证 Policy Evaluation 结果再上线
什么时候用哪种方案
| 需求 | 方案 |
|---|---|
| 按角色区分 API 访问(管理员 vs 普通用户) | Realm/Client Roles + 应用端判断 |
| 同一个 API 按 HTTP 方法区分权限 | Authorization Services + Scope-Based Permission |
| 按用户属性/部门做数据隔离 | Groups + Group-Based Policy + 应用过滤 |
| 需要动态可配置的权限规则 | Authorization Services + JavaScript Policy |
| 第三方应用访问 API 时做 Scope 限制 | OAuth 2.0 Scopes(Client → Client Scopes) |
| 资源由业务动态创建、权限取决于「人与资源的关系」(文档共享、项目协作、多级继承) | 把关系授权外置给 OpenFGA,Keycloak 只做认证,见 Keycloak + OpenFGA |
与其他章节的关联
- RBAC、ABAC、ReBAC 授权模型对比 — 全局视角的授权模型选型
- Keycloak + OpenFGA:IAM 细粒度授权 ReBAC 落地 — 当资源数量与关系复杂度超出 Authorization Services 的适用范围时,把关系授权外置的做法
- Keycloak 架构详解 — Keycloak 内部 Realm/Role/Group 的组织模型
- OAuth 2.0 协议深入 — OAuth Scopes 与 Authorization Server 的工作原理
- Keycloak + oauth2-proxy 集成 — 网关层认证后,应用层授权的配合方式