Files
pikasTech-HWLAB/docs/reference/spec-v02-auth.md
T
2026-06-05 11:11:15 +08:00

25 KiB
Raw Blame History

v0.2 登录与鉴权规格

本文是 HWLAB v0.2 登录、认证和应用层鉴权入口的长期规格。正式用户鉴权只保留两类凭据:浏览器使用 hwlab_session Web sessionCLI/API/AgentRun runner 使用用户级 HWLAB_API_KEY。Keycloak 只负责 Web 身份认证和注册,HWLAB 在 callback 后发行自己的 session、API key 和 AuthPrincipal,避免把 Keycloak token、本地密码、浏览器 cookie 和设备内部系统 key 混用。

基础设施实施跟踪见 pikasTech/HWLAB#788Keycloak 到 HWLAB 的接入收口见 pikasTech/HWLAB#814。用户角色、Code Agent session owner、OpenFGA relation 和资源授权矩阵见 spec-user-access.md;本文只定义“如何登录、如何恢复 actor、如何把请求归一成 actor”。

设计目标

  • Web 用户通过 Keycloak OIDC 登录或注册,HWLAB 不再把本地账号密码表单作为目标登录体验。
  • Web 使用 hwlab-cloud-api 发行的 httpOnly hwlab_session,每个 session token 最长 24 小时;第一版不做复杂 refresh token 管理,但必须可撤销、可重新登录轮换。
  • CLI 是纯 CLI 体验,不打开浏览器、不跳转 Web、不做 device-code flow,也不直接消费 Keycloak access token;标准凭据是环境变量 HWLAB_API_KEY
  • AgentRun runner 内的 hwpod 也必须使用同一类用户 API key 认证,映射到发起 Code Agent session 的 users.idHWLAB_DEVICE_POD_API_KEY 只能作为 cloud-api 到 device-pod 的内部服务凭据或迁移期兼容项,不能作为正式用户鉴权方式。
  • 每个用户在首次登录后自动拥有一个默认 API key;用户也可以在 Web 中创建、查看、失效或重新生成 API key。
  • API key 长效有效,除非用户或管理员手动 revoke/regenerate;它不跟 Web session 的 24 小时过期绑定。
  • Keycloak 只做身份认证和账号注册;HWLAB 的 users.roleusers.status、OpenFGA relation 和 Code Agent owner 仍是应用层授权 source of truth。
  • device-pod 权限只由 users.role/status、Code Agent session owner 和 OpenFGA relation 控制;不存在“对所有正式 device pod 授权”的共享 runner key。

系统边界

组件 职责
Keycloak 独立 keycloak namespace 中的身份提供方,提供公网 HTTPS OIDC issuer、登录页、注册页和用户 subject。
Keycloak Postgres Keycloak 专用数据库,短期可与 Keycloak 同在 keycloak namespace;不复用 HWLAB v0.2 业务 Postgres。
hwlab-cloud-api OIDC callback、session token、API key、用户映射、AuthPrincipal 和应用层授权收口。
hwlab-cloud-web 浏览器 UI 和同源代理;未登录时通过 /auth/oidc/login 引导到 Keycloak,不持有 Keycloak client secret,不直接访问 Postgres。
hwlab-cli / AgentRun hwpod 读取 HWLAB_API_KEY,向 Cloud Web 同源 API 或 Cloud API 发送 Authorization: Bearer hwl_live_...,恢复到同一个用户 actor。
hwlab-edge-proxy / FRP 只转发 cookie/header 和 HTTP 流量,不注入伪 actor,不做业务授权。
HWLAB v0.2 Postgres 保存 HWLAB 用户映射、session、API key、resource grant 和 runtime durable state。

Keycloak 部署不进入 hwlab-v02 namespace。hwlab-v02 只把 Keycloak 当成外部 OIDC issuer 消费,使用 issuer URL、client id 和 client secret SecretRef 完成 callback 校验。

公网 HTTPS 与 issuer

Keycloak 和 HWLAB 浏览器入口都必须使用公网 HTTPSOIDC redirect URI、cookie Secure policy 和浏览器登录都依赖这一点。短期全自动方案使用主 server Caddy + Let's Encrypt + nip.io 域名:Keycloak 通过 FRP 暴露到 auth.74-48-78-17.nip.ioHWLAB Cloud Web 通过 Caddy 反向代理到 v0.2 Cloud Web FRP 入口。

目标公网入口:

Keycloak issuer: https://auth.74-48-78-17.nip.io/realms/hwlab
HWLAB Web/API origin: https://hwlab.74-48-78-17.nip.io

约束:

  • Keycloak admin console、account console、OIDC discovery 和 JWKS 都必须走 HTTPS;不要用裸 HTTP issuer。
  • hwlab-cloud-api 只记录 issuer、client id、redirect URI 和 SecretRef 名称,不在日志、issue 或 ConfigMap 中输出 client secret。
  • Keycloak 的 hostname、proxy header 和 frontend URL 必须与公网 issuer 一致,避免 callback 后 token issuer mismatch。
  • 浏览器和 CLI 默认使用 https://hwlab.74-48-78-17.nip.iohttp://74.48.78.17:19666/19667 只作为 legacy/debug 入口,不作为 OIDC callback、cookie 或用户文档默认入口。
  • Keycloak OIDC client 的 redirect URI 必须指向 HWLAB callbackhttps://hwlab.74-48-78-17.nip.io/auth/oidc/callbackhttps://auth.74-48-78-17.nip.io 只作为 Keycloak issuer/authorization host,不能配置成 HWLAB callback 目标。
  • https://hwlab.74-48-78-17.nip.io/auth/* 由 Cloud Web 同源代理到 Cloud APICloud Web 不读取 client secrettoken exchange 只在 Cloud API 内完成。

Keycloak 当前部署基线

v0.2 的 Keycloak 部署已经具备公网 HTTPS 原生后台管理入口和 hwlab realm OIDC issuer。后续 HWLAB 接入工作应把 Keycloak 当成已存在的外部 OIDC provider 消费,不再把 Keycloak 基础部署、Postgres 或 HTTPS 暴露混入 Cloud Web/Cloud API 接入任务。

部署拓扑:

browser
  -> https://auth.74-48-78-17.nip.io
  -> master Caddy :443
  -> master frps remotePort 28443
  -> G14 keycloak-frpc
  -> keycloak.keycloak.svc.cluster.local:8080

运行基线:

  • Kubernetes namespace 固定为 keycloak,与 hwlab-v02 解耦。
  • Keycloak Deployment 使用 quay.io/keycloak/keycloak:25.0Postgres 使用 postgres:16-alpineFRP client 使用 fatedier/frpc:v0.68.1
  • keycloak Deployment、keycloak-frpc Deployment 和 keycloak-postgres StatefulSet 必须 Readykeycloak-bootstrap-realm Job 必须已成功完成。
  • 公网管理入口是 https://auth.74-48-78-17.nip.io/admin/master/console//admin/ 可以重定向到该原生 console;不要在 HWLAB Cloud Web 中重做 Keycloak 管理 UI。
  • 公网 issuer 是 https://auth.74-48-78-17.nip.io/realms/hwlabdiscovery 与 JWKS 必须全部返回 HTTPS URL。
  • Keycloak management 端口 9000 不作为公网入口暴露;健康和管理探测留在集群内或受控透传内完成。
  • hwlab realm 必须 enabled,并在小范围测试阶段允许 self-registration;新注册用户在 HWLAB 应用层只能默认成为普通 user,不自动拥有 device pod relation。
  • hwlab-cloud-web client 的 redirect URI 指向 https://hwlab.74-48-78-17.nip.io/auth/oidc/callbackweb origin 指向 https://hwlab.74-48-78-17.nip.ioDirect Access Grants 必须关闭,避免把 Keycloak password grant 变成第三种 CLI 鉴权方式。

管理员凭据边界:

  • master realm 的 admin 用户只用于 Keycloak 原生后台和自动化 bootstrap,不是 HWLAB 应用管理员账号。
  • Kubernetes Secret keycloak-admin/password 是 Keycloak 初始管理员和后续自动化 Job 获取 admin token 的凭据来源;如果人工在 Keycloak UI 里轮换 admin 密码,必须同步更新该 Secret,否则重跑 bootstrap/admin REST Job 会继续使用旧密码并失败。
  • 不要把 admin password、client secret、session token 或完整 API key 写入 issue、长期参考、ConfigMap、日志或 trace。临时取密脚本使用后必须删除。
  • hwlab-cloud-web-client Secret 只保存 Keycloak OIDC client secretCloud Web 不直接读取它,只有 hwlab-cloud-api 在 callback/token exchange 中使用。

部署硬化项:

  • Keycloak 当前已能通过公网 HTTPS issuer 和 forwarded headers 正常工作;后续 render 可显式固定 --hostname=https://auth.74-48-78-17.nip.io 和必要的 --hostname-admin,减少代理链路变化导致的 issuer/callback 歧义。
  • 小范围测试可先使用 bootstrap 创建的 client secret;进入更大范围公网使用前应轮换为随机 secret,并同时更新 Keycloak client 与 hwlab-cloud-web-client Secret。

注册策略

短期小范围测试允许 Keycloak 开启自助注册,并且不要求邮箱或手机校验。该策略只有在以下条件同时满足时成立:

  • 新注册用户默认只映射为 HWLAB user 角色,不自动成为 admin
  • 新用户没有 device pod relation,也不能看到或使用任何 device pod,直到 admin 在 HWLAB 中授权。
  • Keycloak 账号状态必须能被管理员禁用;HWLAB users.status='disabled' 也必须能独立阻断 session 和 API key。
  • 如果公网注册出现垃圾账号或撞库迹象,第一优先级是关闭 Keycloak self-registration 或增加邀请码/管理员审核;不要把防滥用逻辑塞进 device pod 授权。

邮箱和手机字段可以作为 profile 信息保存,但第一版不要求验证。后续如果进入更大范围公网使用,应至少补充邮箱验证、注册限流或邀请制中的一种。

身份映射

Keycloak subject 不直接当业务授权 ID。hwlab-cloud-api 在 OIDC callback 中按 issuer + sub 查找或创建 HWLAB users 记录,然后所有业务授权都使用 users.id

推荐扩展 users

ALTER TABLE users ADD COLUMN IF NOT EXISTS auth_provider TEXT NOT NULL DEFAULT 'local';
ALTER TABLE users ADD COLUMN IF NOT EXISTS keycloak_issuer TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS keycloak_sub TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS email TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TEXT;
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_keycloak_subject
  ON users(keycloak_issuer, keycloak_sub)
  WHERE keycloak_issuer IS NOT NULL AND keycloak_sub IS NOT NULL;

映射规则:

  • 首次 OIDC 登录:按 issuer + sub 不存在则创建 users,默认 role='user'status='active'
  • 已存在本地 bootstrap admin 时,可以由 admin 在 HWLAB 内手动绑定 Keycloak subject 或提升 Keycloak 用户为 admin;Keycloak 注册本身不得自动提权。
  • Keycloak profile 更新只同步 username/display_name/email 这类展示信息,不覆盖 HWLAB role/status
  • disabled 用户不能创建 Web session,不能通过 API key 鉴权,也不能继续 Code Agent session。

AuthPrincipal

所有用户态请求最终归一成 AuthPrincipal。业务模块只消费 AuthPrincipal,不直接判断 Keycloak token、cookie 或 API key 原文。

type AuthPrincipal = {
  userId: string;
  username: string;
  displayName: string;
  role: "admin" | "user";
  status: "active" | "disabled";
  authMethod: "web-session" | "api-key";
  sessionId?: string;
  apiKeyId?: string;
  keycloakIssuer?: string;
  keycloakSub?: string;
  scopes: string[];
};

正式用户态请求只接受两类凭据:

  1. Authorization: Bearer hwl_live_...HWLAB_API_KEY 映射出的 header 是用户 API key,适用于 CLI 和 AgentRun runner 内 hwpod
  2. hwlab_session cookie 是 Web session。

本地 /auth/login 只用于空库 bootstrap 和 legacy/debug;它发行的 session 也必须走同一个 hwlab_session cookie,不再通过 Authorization: Bearer <session-token> 恢复用户。device-pod 内部服务 key 只能在 cloud-api 到 device-pod 的受控链路内使用,不出现在用户 API、CLI、AgentRun runner 或浏览器文档中。

Web 登录流程

browser -> Cloud Web /
  -> no valid hwlab_session
  -> GET /auth/oidc/login?returnTo=/
  -> 302 Keycloak login/register
  -> Keycloak callback with code
  -> hwlab-cloud-api exchanges authorization code with confidential client
  -> validate signed id_token via Keycloak JWKS, iss/aud/azp/exp/iat/nonce/sub
  -> fetch userinfo only as profile enrichment and require userinfo.sub == id_token.sub
  -> upsert users by issuer+sub
  -> ensure default API key exists for the user
  -> create user_sessions token hash, expires_at <= now + 24h
  -> Set-Cookie hwlab_session=httpOnly; Secure; SameSite=Lax
  -> redirect returnTo

Web 页面不要求用户输入或复制 API key。浏览器请求通过 hwlab_session 恢复 AuthPrincipal;需要 CLI 时,用户在 Web 的 API key 页面生成或重新生成自己的 key。

Session 规则:

  • 每次 OIDC 登录或 callback 成功都发行新的 session token,数据库只保存 token hash。
  • 单个 session token 最长 24 小时;过期、用户 disabled 或 logout 后必须返回 401 auth_session_invalid
  • /auth/logout 撤销 HWLAB session,并可选择跳转 Keycloak logout;即使 Keycloak 全局会话仍有效,HWLAB 已撤销的 session 也不能继续使用。
  • 第一版不实现长期 refresh token 存储,也不把 Keycloak access token 或 refresh token 写入 HWLAB 数据库;用户过期后重新走 Keycloak 登录。
  • returnTo 只允许同站相对路径,外站 URL、scheme-relative URL、反斜杠或控制字符必须回退到 /workbench

CLI API key 流程

CLI 和 AgentRun hwpod 标准认证方式:

export HWLAB_API_KEY=hwl_live_xxx
bun tools/hwlab-cli/bin/hwlab-cli.ts client auth whoami

约束:

  • CLI 不跳转浏览器,不依赖 Web cookie,不要求 username/password 交互。
  • hwlab-cli 默认从 HWLAB_API_KEY 读取 key,并发送 Authorization: Bearer <key>
  • AgentRun runner 的 transient env 只允许注入映射到当前 Code Agent session owner 的 HWLAB_API_KEY;可以使用该用户默认 key,也可以使用同一 api_keys 表中为该用户创建的 runner 专用 key,但绝不能使用跨用户共享的 device-pod 系统 key 或 Keycloak token。
  • client auth status 必须显示 endpoint、是否检测到 HWLAB_API_KEY、key prefix 和用户摘要;不得输出完整 API key。
  • client auth whoamiclient request GET /v1/users/me 必须能用 API key 返回与 Web 同一用户的 AuthPrincipal 摘要。
  • client auth login --username ... 和本地 cookie profile 属于 legacy 兼容入口;目标体验不再把它作为一等 CLI 登录。
  • hwpod 在 runner 中必须自动使用 HWLAB_RUNTIME_API_URLHWLAB_API_KEY 直连 hwlab-cloud-api,并让 cloud-api 按该 key 对应用户的 role/grant 做授权。

API key 数据结构:

CREATE TABLE IF NOT EXISTS api_keys (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  name TEXT NOT NULL DEFAULT 'Default API key',
  key_prefix TEXT NOT NULL,
  key_hash TEXT,
  display_secret TEXT,
  scopes_json TEXT NOT NULL DEFAULT '[]',
  status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
  created_at TEXT NOT NULL,
  last_used_at TEXT,
  revoked_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id, status, created_at DESC);
CREATE UNIQUE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(key_prefix);

目标状态只长期保存 key_hashkey_prefix,完整 API key 只在创建或 regenerate 响应中显示一次;用户丢失 key 时通过 regenerate 获取新 key。display_secret 只允许作为迁移期开发字段存在,不能作为生产化长期口径;完整 key 不得写入日志、issue、ConfigMap、Kubernetes Secret、trace 或 CLI 默认输出。

API key 行为:

  • 首次 OIDC 登录后自动创建默认 key;如果默认 key 被 revoke,用户可在 Web 中重新生成。
  • 默认 key、手动创建的 key 和 AgentRun runner 使用的 key 都继承该用户的 role/status/grant,不额外绕过权限矩阵。
  • revoke 后同一 key 立刻返回 401 api_key_invalid,不能等 Web session 过期。
  • 每次 API key 成功认证应更新 last_used_at,但不得记录完整 key。

API 接口

接口 说明
GET /auth/oidc/login?returnTo=... 发起 Keycloak OIDC 登录,保存 state/nonce 并 302 到 Keycloak。
GET /auth/oidc/callback 校验 code、issuer、nonce 和 state,映射用户,发行 24 小时 Web session。
GET /auth/session 从 Web cookie 恢复 actorAPI key 用户摘要使用 /v1/users/me
POST /auth/logout revoke 当前 Web session 并清 cookie。
POST /auth/login 本地账号密码 legacy/bootstrap fallback;目标 Web/CLI 登录不依赖该入口。
GET /v1/users/me 返回当前 AuthPrincipal 的脱敏摘要。
GET /v1/api-keys 列出当前用户 API key metadata;目标状态不返回完整 key。
GET /v1/api-keys/default 返回或创建默认 API key;目标状态只在新建时返回一次完整 key。
POST /v1/api-keys 创建一个用户 API key。
POST /v1/api-keys/{id}/regenerate 重新生成指定 API key,旧 secret 立即失效。
DELETE /v1/api-keys/{id} revoke 指定 API key。

所有 API key 管理接口都要求当前 AuthPrincipal 是同一用户本人或 admin;普通用户不能查看或生成其他人的 key。常规列表响应只返回 key_prefixidnamestatuslast_used_at,完整 key 只在创建或 regenerate 的一次性响应中出现。

与资源授权的关系

登录和认证只回答“请求是谁”。资源授权仍由 spec-user-access.md 定义:

  • admin 可以管理用户、device pod profile/relation,并跨用户查看或取消 Code Agent session。
  • user 只能访问自己的 Code Agent session 和被授权的 device pod。
  • Web session、CLI API key 和 AgentRun runner 内 hwpod API key 得到同一个 users.id 时,应看到相同 OpenFGA device pod relation 和账号 workspace。
  • Keycloak realm role、group 或 claim 不直接决定 HWLAB device pod 权限;最多作为创建/绑定用户时的输入线索。

测试规格

T1

阅读 docs/reference/spec-v02-auth.md,然后用 CLI 或 curl 验证 https://auth.74-48-78-17.nip.io/realms/hwlab/.well-known/openid-configuration 可访问,issuer 与配置完全一致,JWKS URL 为 HTTPS,响应不包含 admin password、client secret 或数据库连接串。

T2

阅读 docs/reference/spec-v02-auth.md,然后从浏览器访问 https://hwlab.74-48-78-17.nip.io/。未登录时必须进入 Keycloak 登录/注册;注册新用户不要求邮箱或手机验证,callback 后回到 WorkbenchGET /auth/session 返回 active user actor 和 24 小时内过期的 session,不返回 session token 原文。

T3

阅读 docs/reference/spec-v02-auth.md,然后登录 Web 后打开 API key 管理入口,确认可以创建或 regenerate 用户 API key;完整 key 只在创建或 regenerate 响应中显示一次。用 HWLAB_API_KEY=<key> bun tools/hwlab-cli/bin/hwlab-cli.ts client auth whoami 验证 CLI 无浏览器跳转即可恢复同一用户。

T4

阅读 docs/reference/spec-v02-auth.md,然后 revoke 或 regenerate API key,再用旧 HWLAB_API_KEY 请求 /v1/users/me/v1/device-pods,必须返回 401 api_key_invalid;同一用户重新登录 Web session 不应恢复旧 key。

T5

阅读 docs/reference/spec-v02-auth.md,然后分别使用 Web session 和同一用户的 CLI API key 访问 /v1/device-pods、创建 Code Agent session 和读取自己的 trace/result,确认授权结果一致;另一个普通用户的 API key 不能读取该 sessionadmin 可以跨用户查看。

T6

阅读 docs/reference/spec-v02-auth.md,然后检查 cloud-api 日志、trace、CLI 默认输出和 issue 复现材料,确认不出现 Keycloak client secret、session token、完整 API key、历史 HWLAB_DEVICE_POD_API_KEY 或 Postgres URL 原文;AgentRun runner 中也不得存在跨用户共享的 device-pod 系统 key。

规格的实现情况

规格项 状态 说明
Keycloak 独立 namespace 与公网 HTTPS issuer 部署已完成 keycloak namespace、Caddy/FRP HTTPS、hwlab issuer、admin console 和 bootstrap Job 已形成部署基线;后续只按本文件继续硬化。
Keycloak 自助注册且不强制邮箱/手机验证 Keycloak 侧已完成 只适合小范围测试;HWLAB 应用层仍必须默认 user、无 device pod relation。
Web OIDC login/callback 待 HWLAB 接入收口 Keycloak client、Cloud API/Web rollout 和浏览器 callback 验收见 #814redirect URI 必须使用 https://hwlab.74-48-78-17.nip.io/auth/oidc/callback
Web session 24 小时轮换 待 HWLAB 接入收口 当前目标是 callback 成功后由 hwlab-cloud-api 发行 24 小时 hwlab_session;验收见 #814。
CLI/AgentRun HWLAB_API_KEY 一等登录 待 HWLAB 接入收口 目标是统一 env API key 映射到用户,无浏览器跳转,无 Keycloak token,无跨用户 device-pod key;验收见 #814。
API key 一次性显示和 revoke/regenerate 待 HWLAB 接入收口 目标状态只在创建或 regenerate 时显示完整 key;列表和已存在默认 key 只返回 metadata。
AuthPrincipal 归一 待 HWLAB 接入收口 后续实现必须把 Web session、CLI API key 和 AgentRun hwpod API key 都归一成同一用户 actorlegacy/internal key 不作为正式用户方法。
admin/user 与 device pod relation 授权 部分实现 现有 cloud-api 已有本地用户、session、OpenFGA relation 和 Code Agent owner 绑定;资源授权继续按 spec-user-access 收敛。

Keycloak 部署纪律

G14 v0.2 固定使用 quay.io/keycloak/keycloak:25.0(实际启动版本 Keycloak 25.0.6)。该镜像的初始管理员入口是 KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORDKC_BOOTSTRAP_ADMIN_USERNAME/PASSWORD 属于更新版本文档口径,不作为 v0.2 当前镜像的部署入口。master realm 保持 Keycloak 自身初始化结果,不导入或覆盖;业务 realm 使用管理员 token 通过 Admin REST API 创建和更新。

--import-realm 只用于导入非 master 的预制 realm 文件,且启动导入遇到已存在 realm 会跳过。需要覆盖已存在 realm 时按 Keycloak import/export 文档停服务后运行离线 import,不能在运行中的生产 Deployment 上把 master realm 当作可覆盖配置文件反复灌入。

禁止直接修改 keycloak 数据库

所有 keycloak admin 操作必须通过 kc.sh CLI、Keycloak admin REST API 或 --import-realm 启动导入完成。禁止keycloak namespace 里的 Postgres 库直接执行 INSERT / UPDATE / DELETE 修改 user_entitycredentialrealmclientclient_scopeclient_scope_attributeskeycloak_roleuser_role_mapping 等表。直接改库会导致:

  • 破坏 Keycloak 25 的 pbkdf2-sha256 JSON credential 格式,登录时报 Unrecognized token 'pbkdf2' 反序列化失败
  • 绕过 Keycloak 启动时缓存的 client_scope_attributes(如 include.in.token.scope),pod 重启后修改失效
  • 跳过 Liquibase changelog,未来 keycloak 升级时迁移冲突
  • 在多副本或滚动重启场景下造成各副本行为不一致

正确做法

场景 推荐做法 禁止做法
第一次引导管理员 KEYCLOAK_ADMIN=admin + KEYCLOAK_ADMIN_PASSWORD 引用 keycloak-admin Secret,让 Keycloak 在首次启动时自行创建 master 管理员 psql 直插 user_entity + credential,或导入自制 master-realm.json 覆盖 Keycloak 内置 master realm
创建 hwlab realm + hwlab-cloud-web OIDC client keycloak-bootstrap-realm Job 用 kcadm.sh / Admin REST API 幂等 create-or-update realm/clientredirect URI 对齐 hwlab-cloud-api callback psql 直插,或依赖运行中的 --import-realm 覆盖已有 realm
修改 realm/client/user/role 优先通过 Keycloak Admin REST API 或 kcadm.sh;批量离线导入必须停服务后按 import/export 文档执行,并先确认是否会覆盖目标 realm psql UPDATE 后靠重启碰运气
调试 admin token 没 realm_access.roles kubectl -n keycloak exec ... -- /opt/keycloak/bin/kc.sh show-config 查 attribute 实际值;用 curl /admin/realms/master/client-scopes/<id> 走 admin REST 验 attribute 状态 psql UPDATE 然后重启 keycloak pod 试运气

历史教训

#788 收口过程中曾用 psql 直插 user_entity / credential / user_role_mapping 临时绕开 Keycloak 初始化问题,结果:

  1. 直插的 pbkdf2-sha256 字符串 hash 在 Keycloak 25 反序列化时抛 Unrecognized token 'pbkdf2'
  2. SQL UPDATE client_scope_attributes.include.in.token.scope = 'true' 后,access_token 仍然不包含 realm_access.roles——Keycloak 在启动时缓存 attributeSQL 修改要重启 pod 才生效
  3. client_scope_client.default_scope = true 的 SQL 修改同样需要重启才生效
  4. master realm admin role 复合 19 个子 role,手工插 user_role_mapping 后仍然有缺失

所有这些都是直接改库的副作用。后续所有 Keycloak 部署必须走镜像支持的初始管理员 env、Admin REST API、kcadm.sh 或明确停服的离线 import禁止 SQL 直改