Keycloak 授权体系详解:资源、策略、权限

本文基于一个真实的菜单权限控制场景,梳理 Keycloak 中资源(Resource)、策略(Policy)、权限(Permission)三者的关系,并重点回答一个常见疑问:既然资源和权限已经够用,为什么还要有策略这一层?

适合在日后忘记细节时翻看复习。


一、背景:我的菜单权限场景

前端项目中有若干菜单,每个菜单根据用户权限决定是否显示。我原本的做法是:

  1. 每个路由菜单关联一个字段 menuPerms(权限标识符),例如 menu:user:listmenu:order:view

  2. 把这些 menuPerms 全部导入 Keycloak 的客户端资源中。

  3. 希望登录时根据用户返回其有权访问的资源集合。

  4. 前端用这个资源集合去匹配菜单路由,决定渲染哪些菜单。

目标很明确:让 Keycloak 成为菜单权限的唯一数据源,前端只负责根据返回的资源集合过滤菜单。


二、核心三要素:资源、策略、权限

Keycloak 的授权服务由三个核心概念构成,它们是一条从「保护什么」到「根据什么规则允许」的递进链路。

1. 资源(Resource)

定义 :授权策略所要保护的对象

  • 在你的场景中,每一个 menuPerms 标识符就是一个资源,例如 menu:user:list

  • 资源是静态的、被动的,它只回答「有哪些东西需要被保护」。

  • 资源可以定义作用域(Scope),例如 vieweditdelete,用来区分对同一资源的不同操作。

2. 策略(Policy)

定义 :授予访问权限必须满足的条件

  • 策略本身不关心具体的资源,只回答「满足什么条件的人可以访问」。

  • 例如:基于角色的策略(用户拥有 admin 角色)、基于用户的策略(用户是张三)、基于组的策略、基于时间的策略等。

  • 策略是可复用的,一个策略可以应用到多个权限中。

3. 权限(Permission)

定义 :连接资源策略桥梁 ,回答「 (策略)能对什么 (资源)执行什么操作」。

  • 创建权限时,需要指定:保护哪个资源、应用哪些策略、允许哪些作用域。

  • 权限是最终做出「授予」或「拒绝」裁决的地方。

  • 只有当权限所关联的策略评估通过时,该资源对该用户的访问才会被允许。

三者的关系图

text

复制代码
资源 (Resource)         权限 (Permission)          策略 (Policy)
  保护对象        ←────  桥梁/裁决点   ────→      判断条件
  menu:user:list         权限A                    角色策略:admin
  menu:order:view         ├─ 保护 menu:user:list   角色策略:财务
                          └─ 引用 角色策略:admin

一句话概括:权限 = 资源 + 策略。资源是「保护什么」,策略是「凭什么」,权限是「把两者绑在一起并做出裁决」。


三、我的疑问:既然资源和权限够用,为什么还要策略?

这是最关键的疑问。如果只是「角色 → 资源」这种简单映射,看起来只需要:

  • 创建资源 menu:user:list

  • 创建权限,保护该资源,直接绑定角色 admin

这样就变成了「当前角色拥有当前资源」,策略层似乎完全多余。

为什么 Keycloak 还要在中间加一层策略?


四、策略层存在的意义

1. 复用:一处定义,多处引用

假设没有策略层,权限直接绑角色:

  • 权限 A 保护资源 1,绑定角色 admin

  • 权限 B 保护资源 2,绑定角色 admin

  • 权限 C 保护资源 3,绑定角色 admin

当规则变化,比如「admin 还必须属于财务部才能访问」时,你必须在每一个权限里逐个修改判断条件。权限一多,维护成本爆炸。

有了策略层:

  • 创建一个策略「admin 且属于财务部」

  • 权限 A/B/C 都引用这一个策略

规则变化时,只改策略一处,所有引用它的权限自动生效。

2. 组合:支持多维判断

Keycloak 的策略支持逻辑组合:

  • Aggregate Policy(聚合策略):把多个策略用 AND/OR 组合。例如「(是 admin) OR (是财务 AND 已认证)」。

  • 策略可以嵌套引用策略。

没有策略层,判断维度只能是「角色」这一个。有了策略层,可以自由组合:角色 + 用户属性 + 时间 + 客户端 + 上下文。

3. 多种策略类型:不止是角色

Keycloak 内置的策略类型远不止角色:

策略类型 判断依据
Role Policy 用户是否拥有某角色
User Policy 是否是特定用户
Group Policy 用户是否在某个组
Client Policy 请求来自哪个客户端
Time Policy 当前时间是否在范围内
Aggregated Policy 上述策略的逻辑组合
JS Policy 自定义 JavaScript 逻辑
Regex Policy 基于声明的正则匹配

没有策略层,这些判断逻辑只能硬编码进权限,或者根本无法表达。有了策略层,可以写出「只有来自移动端 App、且用户属于 VIP 组、且在工作时间内」才能访问某资源这样的规则。

4. 关注点分离:决策与绑定解耦

  • 权限回答:「哪个资源,允许哪些操作,由哪些策略来裁决」

  • 策略回答:「满足什么条件才算通过」

这两件事的变化频率和变化原因不同:

  • 资源/权限的调整通常跟业务对象变化有关。

  • 策略的调整通常跟安全规则变化有关。

分开后,改安全规则不会动到资源绑定关系,改资源绑定也不会动到安全规则。

5. 支持更细粒度的决策:为 ABAC 铺路

策略评估的结果不只是「通过/不通过」,还可以携带信息,供权限层做更复杂的裁决(多个策略满足任意一个即可,或必须全部满足)。这为 ABAC(基于属性的访问控制) 提供了基础,而纯角色绑定只能做 RBAC(基于角色的访问控制)

6. 可审计、可解释

当需要回答「这个用户为什么能看这个菜单」时,策略层让决策链路清晰可查:哪个权限、引用了哪些策略、每个策略评估结果如何。纯绑定角色则无法提供这种解释能力。


五、正确实现路径(结合我的菜单场景)

我原本的思路是「给每个用户分配这些策略」,但正确做法是给权限分配策略,而不是直接给用户分配策略。完整路径如下:

步骤 1:将菜单权限标识符创建为资源

把每个 menuPerms(如 menu:user:list)导入 Keycloak 的客户端资源中。

步骤 2:创建可复用的策略

根据业务规则创建策略。例如:

  • 角色策略「拥有 menu:view 角色」

  • 组策略「属于财务组」

步骤 3:创建权限并关联资源与策略

为菜单资源创建基于资源的权限

  • 选择要保护的菜单资源(如 menu:user:list

  • 关联上创建好的策略

这样,任何满足策略条件的用户,在请求这个菜单资源时,权限都会评估通过。

步骤 4:登录时获取用户有权访问的资源集合

用户登录时,前端应用调用 Keycloak 的 Entitlement API (权利端点)或发送特定的令牌请求,获取该用户在当前客户端下所有被授予权限的资源列表

  • 接口示例:GET /authz/entitlement/{client_id}

  • 返回的 RPT(请求方令牌)中会包含授权信息。

步骤 5:用返回的资源集合过滤菜单

Keycloak 返回的响应中包含用户有权访问的资源名称(即 menuPerms 标识符)。前端拿到资源集合后,去匹配预先定义好的菜单路由,只渲染匹配成功的菜单项。


六、关键实现提示与坑

1. 使用 Entitlement API

获取用户资源权利的接口是 Entitlement API,返回的 RPT 中包含授权信息。这是实现动态菜单的关键。

2. 谨慎对待默认配置

Keycloak 会为新的资源服务器创建一个默认资源 (如 /*)和默认策略 (总是授予)。这个默认策略会允许访问任何资源,务必在自定义配置前删除或修改它,否则你的权限配置将失效。

3. 渐进式采用策略

如果场景目前确实很简单(「某角色能看到某菜单」),用「权限直接绑角色」也能跑通。可以先用资源和权限跑通,等规则复杂了再引入策略。这是完全合理的渐进式做法。


七、一句话总结

资源 是保护对象,策略 是判断条件,权限是把两者绑在一起并做出裁决的桥梁。

策略层的本质,是把「判断谁能访问」的逻辑从「资源与角色的绑定关系」中抽离出来,使其成为可复用、可组合、可动态评估的独立单元。简单场景可以不用它,但一旦访问控制规则变复杂,没有策略层就会导致规则散落在成百上千个权限配置里,无法维护。

Keycloak 的设计不是「为了多一层而多一层」,而是为了支撑从简单 RBAC 到复杂 ABAC 的平滑演进。


八、速查表

概念 回答的问题 我的场景中的对应物
资源 Resource 保护什么? 每个 menuPerms 标识符
策略 Policy 凭什么条件允许? 角色策略、组策略等
权限 Permission 谁能在什么资源上做什么? 保护某菜单资源 + 引用某策略
Entitlement API 用户有权访问哪些资源? 登录时调用,返回资源集合
默认策略 兜底规则(危险) 必须删除或修改,否则权限失效
相关推荐
志尊宝3 小时前
Vue3 零基础每日笔记(047):动态路由与路由参数——:id 传参、query 传参、props 解耦
前端·javascript·vue.js·笔记·html5
黑马程序员毕设3 小时前
基于微信小程序的二手交易平台设计与实现报告
前端·人工智能·spring boot·后端·考研
cAuth3 小时前
实现一个图形编辑器
前端·架构·计算机图形学
Nayana4 小时前
《Web 到 HarmonyOS》-- 嵌入 Web 与桥接基础
前端
一拳不是超人4 小时前
之前用 AI 两小时写的塔罗网站,我真把它做上线了,然后呢?
前端·人工智能·程序员
flash俊杰5 小时前
ASR 转写与字幕时间轴:强制对齐、CPS 约束与 SRT 工程化
前端
前端炒粉5 小时前
AI工具面经
前端
唐小码5 小时前
裁员的风刮到了三线荒凉的城市
前端