LabHub
学习 学习路径 课程

Keycloak 与企业认证

realm 是隔离边界

在 LabHub 中继续学习

一句话总结

Realm 是容纳用户、客户端、角色和密钥的完整隔离单元。不同 Realm 之间互不知道对方的用户。

概念图: 签名 · 签发者(iss) · 受众(aud) · 过期时间(exp)

为什么需要了解这一点

第一次打开 Keycloak 时,会看到 master Realm。把应用用户创建在这里,是第一个常见错误。master 是用于管理 Keycloak 自身的 Realm,其中的用户会与 Keycloak 管理权限关联。应用必须单独创建 Realm。

Realm 是隔离单元,这句话有非常具体的含义:每个 Realm 都有不同的签名密钥、用户存储和令牌签发者(iss)。因此,Realm A 的令牌无法通过 Realm B 的 API 验证。多租户 SaaS 为每个租户设置 Realm 的设计正是由此而来。不过,Realm 达到数百个后,管理和内存负担都会增大,因此通常会在一个 Realm 内通过组和属性区分租户。

工作原理

客户端是在 Realm 内请求令牌的应用,分为两类。

公共客户端没有秘密。SPA 和移动应用属于这一类,必须启用 PKCE。准确注册重定向 URI 也很重要——若通配符范围过宽,就会形成窃取令牌的通道。

机密客户端拥有秘密。后端服务器属于这一类;启用服务账号后,可通过 client credentials 流程获取代表自身的令牌。

角色分为两个层次。Realm 角色在整个 Realm 中有意义(例如 admin),客户端角色只在特定客户端中有意义(例如 orders-apirefund)。服务数量较多时,使用客户端角色能够避免名称冲突。

组是用户的集合,可以映射角色。与其逐个给 300 名用户分配角色,不如把角色分配给组,再把用户加入组。组可以形成层级,子组会继承上级组的角色。

复合角色指一个角色包含其他角色。例如让 order-admin 包含 order-reader 后,就不必分别给管理员分配两个角色。

实际工作中的表现

令牌中角色所在的位置很容易混淆。Realm 角色放在 realm_access.roles,客户端角色放在 resource_access.<클라이언트ID>.roles。如果关闭客户端的“full scope allowed”,未映射到该客户端的角色会从令牌中消失。这可用于减小令牌并维持最小权限;但若不了解这一点就关闭,会出现“明明分配了角色,令牌里却看不到”的问题。

管理操作最好用 kcadm.sh 编写脚本。通过 Web 控制台手工创建的配置无法复现,预发布环境和生产环境会在不知不觉中变得不同。

验证令牌的一方必须检查什么

无论签发端配置得多好,如果接收端没有正确验证,就毫无意义。API Server 收到令牌后,要检查的内容是明确的。

这里绝对不能让令牌决定使用哪种算法。 如果验证方直接相信头部中的 alg,攻击者就可能把它改成 none 或对称算法来绕过签名。应在代码中固定预期算法,若不同则立即拒绝。

令牌寿命也应一并设计。访问令牌较短(数分钟)、刷新令牌较长,是因为访问令牌无法被撤销。只要签名有效且尚未过期,即使在 Keycloak 中删除用户,该令牌仍然有效。因此,需要立即封禁时,要么依赖短寿命,要么改为每个请求都检查令牌状态;后者会让所有请求都经过 Keycloak,必须同时考虑性能和可用性。“已经退出登录,为什么还能使用?”这个问题的答案大多就在这里。

最后,还要决定验证失败时返回什么。签名或签发者错误应返回 401;令牌有效但该角色无权执行操作,则返回 403。若混为一谈,客户端无法区分应该重新登录还是应该申请权限,最终可能做出无限重复登录的页面。

下个练习将做什么

创建 Realm,分别创建公共客户端与机密客户端,再创建用户。之后在独立练习中创建角色和组,确认它们如何反映到令牌中,并尝试通过 scope 排除角色。