Keycloak 配置登录流程以限制用户和群组对于客户端的访问

  事情的起源,是我在配置 Home Assistant 的 OIDC 登录时,发现 Home Assistant OIDC 集成 并不支持直接限制单一用户、群组的访问,联想到之前在配置 Unraid 和堡垒机时也遇到了类似的权限控制问题,所以开始研究是否可以在 Keycloak 端增加访问权限的控制。

Info

在 OIDC 流程的规范设计上,理论应该由客户端来控制权限,Keycloak 作为 OIDC 认证提供方,应该仅关注用户的身份是否合规。此处进行这种探索实属无奈之举。

解决方案

  经过研究,我发现可以通过 Keycloak 的 认证流程(Authentication Flow) 来实现基于角色的访问控制。核心思路是在认证流程中添加角色检查条件,当用户不满足特定角色要求时,直接拒绝访问。

  这个方案的原理其实很简单:Keycloak 的认证流程是由一系列「执行器」组成的,每个执行器负责验证用户的某一方面(如密码、OTP 等)。我们可以在这个流程中插入一个「条件判断」,检查用户是否具备特定角色,如果不具备则直接拒绝认证。

实现步骤

第一步:复制现有认证流程

  首先,我们需要基于现有的认证流程创建一个副本。之所以要复制而不是直接修改,是因为默认的 Browser 流程可能被多个客户端共享使用,直接修改会影响到其它不需要权限控制的客户端。

  1. 登录 Keycloak 管理控制台
  2. 进入 Authentication → Flows
  3. 选择 browser 流程
  4. 点击右上角的 Copy 按钮
  5. 为新流程命名(如:browser-with-role-check

第二步:创建条件子流程

  在复制的流程中添加一个条件子流程,用于角色验证。这个子流程将作为一个「关卡」,只有通过了角色检查的用户才能继续后续的认证流程。

  1. browser-with-role-check 流程中,找到 Browser Forms 步骤
  2. 点击右侧的 Actions → Add flow
  3. 设置流程信息:

    • Alias: Access By Role(或其他描述性名称)
    • Flow Type: 选择 Generic
  4. 点击 Save
  5. 确保新创建的子流程的 Requirement 设置为 CONDITIONAL

Info

将 Requirement 设置为 CONDITIONAL 是关键,这意味着该子流程只有在其内部的条件满足时才会执行。

第三步:配置角色条件检查

  为子流程添加角色验证执行器。这是整个方案的核心部分,我们需要告诉 Keycloak「当用户不具备某个角色时,触发后续的拒绝操作」。

  1. 选中刚创建的 Access By Role 子流程
  2. 点击 Add execution
  3. 选择 Condition - User Role
  4. Requirement 设置为 REQUIRED
  5. 点击右侧的 Actions → Config,配置以下参数:

    • Alias: admin-role-missing(便于识别的别名)
    • User role: admin(填写你实际使用的角色名称)
    • Negate output: 勾选(启用取反逻辑)
  6. 点击 Save

Warning

Negate output 设置为 true 意味着「当用户不具备指定角色时,条件才成立」。这是一个容易混淆的地方,请务必理解清楚:我们希望的是「没有角色的用户触发拒绝」,而不是「有角色的用户触发拒绝」。

第四步:添加拒绝访问执行器

  当角色检查条件满足(即用户缺少所需角色)时,执行访问拒绝操作。

  1. 仍在 Access By Role 子流程中,点击 Add execution
  2. 选择 Deny Access
  3. Requirement 设置为 REQUIRED

第五步:绑定认证流程到客户端

  最后,将新创建的认证流程应用到需要权限控制的客户端。这一步是让我们的配置生效的关键。

  1. 进入 Clients → 选择目标客户端(如 Home Assistant)
  2. 切换到 Advanced 标签页
  3. 找到 Authentication Flow Overrides
  4. Browser Flow 设置为 browser-with-role-check
  5. 点击 Save

工作原理

  整个流程的逻辑如下:

  1. 用户访问客户端,被重定向到 Keycloak 进行认证
  2. 用户完成常规的用户名密码验证
  3. 进入 Access By Role 条件子流程
  4. Condition - User Role 执行器检查用户是否具备 admin 角色
  5. 如果用户不具备该角色(因为我们启用了 Negate output),条件成立
  6. Deny Access 执行器被触发,用户收到访问拒绝的错误
  7. 如果用户具备该角色,条件不成立,跳过拒绝操作,认证成功

进阶配置

基于组的控制

  如果你希望使用组而非角色进行控制,可以采用以下方案:

  1. 创建一个特定组(如 allowed-users
  2. 为该组分配一个专用角色(如 can-access-homeassistant
  3. 在认证流程中检查该角色

  这种方式的好处是可以通过组来批量管理用户权限,而不需要为每个用户单独分配角色。

多客户端复用

  同一个认证流程可以绑定到多个客户端,只需在各客户端的 Authentication Flow Overrides 中进行相同配置即可。但需要注意的是,如果不同客户端需要检查不同的角色,则需要为每个客户端创建单独的认证流程。

注意事项

Warning

这种方案存在以下局限性:

  • 违背了 OIDC 的设计原则(权限控制应由客户端处理)
  • 可能影响用户体验(被拒绝的用户会看到通用错误信息,而非友好的「您没有权限访问此应用」提示)
  • 需要为每个需要权限控制的客户端单独配置流程绑定

  尽管存在这些不足,但对于不支持原生权限控制的 OIDC 客户端(如本文场景中的 Home Assistant、Unraid 等),这仍是一个实用的变通方案。如果你的客户端支持原生的权限控制功能,我仍然建议优先使用客户端侧的方案,这样更符合 OIDC 的设计理念,也能提供更好的用户体验。

添加新评论