场景

你已经在 Keycloak 里配好了 Realm、Client 和用户,SSO 登录也跑通了。现在业务方提了三个需求:

  1. “这个 API 只有管理员能调,普通用户不行”
  2. “同一个 API,部门 A 的人能写、部门 B 的人只能读”
  3. “用户可以查看自己的订单,但不能看别人的”

你在 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

GroupRole
本质用户的组织归属用户的权限标签
典型值engineering、sales、cn-teamadmin、viewer、order-manager
能嵌套吗✅ 支持子 Group✅ 通过 Composite Role 嵌套
适合干什么按部门/团队分组、LDAP 映射授予具体权限、做 RBAC
不适合干什么直接做权限判断表达部门层级关系

简单原则:Group 说「你是谁的人」,Role 说「你能干什么」。一个用户可以有多个 Group 和多个 Role。

Realm Role vs Client Role

Realm RoleClient Role
作用域整个 Realm 通用仅某个 Client 可见
典型场景global-admin、auditorapp-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-viewer

2. 创建 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:

字段值说明
NameOrders API资源名称
Display name订单管理 API显示名称
URIs/api/orders/*匹配的 URI 模式
Scopesview, create, edit, delete该资源支持的操作

Step 3: 创建 Policies

Authorization → Policies → Create policy → Role:

Policy 1: 管理员完全访问

字段值
NameAdmin Full Access
Rolesapp-admin
LogicPositive

Policy 2: 普通用户只读

字段值
NameUser Read Only
Rolesapp-user
LogicPositive

Step 4: 创建 Permissions

Authorization → Permissions → Create permission → Scope-Based:

Permission 1: 管理员全部操作

字段值
NameAdmin can manage orders
ResourcesOrders API
Scopesview, create, edit, delete
PoliciesAdmin Full Access
Decision StrategyAffirmative

Permission 2: 普通用户只能查看

字段值
NameUser can view orders
ResourcesOrders API
Scopesview
PoliciesUser Read Only
Decision StrategyAffirmative

Step 5: 测试 Policy Evaluation

Authorization → Evaluate:

  1. 输入测试用户
  2. 选择 Resource: Orders API
  3. 选择 Scope: edit
  4. 如果用户是 app-admin → PERMIT
  5. 如果用户是 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。

场景

  • 浙江分公司的员工只能查看浙江的订单
  • 上海分公司的员工只能查看上海的订单

配置

  1. 创建 Groups: /china/zhejiang、/china/shanghai
  2. 创建 Group-based Policy:Zhejiang Users(绑定 /china/zhejiang Group)
  3. 创建 Resource: Orders-Zhejiang(URI: /api/orders?region=zhejiang)
  4. 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 请求返回 403Client 未启用 Authorization ServicesClient → Settings → Authorization Enabled
JWT 中没有 Groups缺少 Group Mapper添加 Group Membership Mapper
改了策略不生效Authorization 缓存清除 Keycloak 缓存或重启;生产环境设短 TTL
resource_access 为空Client 没有定义 Client Roles先在 Client → Roles 创建角色
Composite Role 的子角色不生效子角色分配到了 Realm Role 而非同一 ClientComposite 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

与其他章节的关联