Keycloak Admin API 使用详解
Admin REST API 逐域实测:认证、Realm、用户、客户端、组、角色的端点/参数/响应与 401/409 错误对照。
Keycloak Admin API 使用详解
> 目标:以 curl 为工具,逐域实测 Keycloak Admin REST API(认证、Realm、用户、客户端、组、角色)的端点、方法、参数与真实响应,覆盖 200/201/204/400/401/403/404/409 全部关键状态码,全部命令与回显在 Ubuntu 22.04 + Keycloak 26.8.1 环境逐条实测通过。
一、环境信息
- 操作系统:Ubuntu 22.04(x86_64)
- Keycloak:26.8.1,安装目录
/opt/keycloak,服务地址http://localhost:8080 - 管理员:
admin/q1w2e3r4(master 域,随首启引导创建) - 业务域:
quzuo-demo;公开客户端quzuo-app;机密客户端quzuo-service(secret:qz-svc-secret-2026) - 测试用户:
zhangsan/q1w2e3r4(用户 UUID:9e863c96-f97b-4d9c-989e-3088a72a8ae0)、临时用户lisi(用于删除演练) - 客户端工具:curl(任意 7.x 以上版本即可,无需 SDK)
💡 说明:Admin API 与业务 OIDC 端点共用 8080 端口,管理路径前缀为
/admin,无需开启额外端口。
二、前置条件
2.1 端点与版本确认
Admin API 的基址为 http://localhost:8080/admin,OpenAPI 文档由服务自动暴露在 /admin/master/openapi.json。先确认服务可用与版本:
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:8080/
/opt/keycloak/bin/kc.sh --version
实测回显:
HTTP 302
Keycloak 26.8.1
JVM: 21.0.12.1 (Red Hat, Inc. OpenJDK 64-Bit Server VM 21.0.12.1+1-LTS)
OS: Linux 6.8.0-138-generic amd64
💡 说明:首页 302 是未登录访问根路径跳向管理控制台的正常行为,能拿到 302 即说明服务已监听 8080。
2.2 认证方式
Admin API 使用 OAuth 2.0 Bearer Token 认证:用内置客户端 admin-cli 在 master 域以 password grant 取 token,之后每个请求携带 Authorization: Bearer <token>。本篇把 token 存入变量 $AT 复用:
AT=$(curl -s -X POST http://localhost:8080/realms/master/protocol/openid-connect/token \
-d 'grant_type=password&client_id=admin-cli&username=admin&password=q1w2e3r4' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
echo "AT_LEN=${#AT}"
实测回显(token 为 750 字符的 JWT,此处仅显示长度):
AT_LEN=750
⚠️ 注意:admin-cli 的 password grant 需要管理员账号本身(master 域用户)具备管理角色,随首启引导创建的
admin默认具备;token 有效期默认 1800 秒,过期后重新执行上述命令即可。
三、API 总览
| 资源域 | 基路径 | 典型方法 |
|---|---|---|
| Realm | /admin/realms | GET / POST / DELETE |
| 用户 | /admin/realms/{realm}/users | GET / POST / PUT / DELETE |
| 客户端 | /admin/realms/{realm}/clients | GET / POST / DELETE |
| 组 | /admin/realms/{realm}/groups | GET / POST / PUT / DELETE |
| 角色 | /admin/realms/{realm}/roles | GET / POST / DELETE |
| 角色映射 | /admin/realms/{realm}/users/{id}/role-mappings | GET / POST / DELETE |
表后解读:所有写操作(POST/PUT/DELETE)成功时不返回 JSON 体,而是用状态码表达结果——创建成功是 201 Created(Location 头给出新资源地址),更新/删除成功是 204 No Content;失败时才返回 JSON 错误体。写脚本时以状态码为判据,不要试图解析空响应体。
四、认证与首次调用
4.1 首次调用:列出全部 Realm
curl -s http://localhost:8080/admin/realms -H "Authorization: Bearer $AT" \
| python3 -c 'import sys,json;[print(r["realm"], r["id"]) for r in json.load(sys.stdin)]'
实测回显:
master 48919f5d-846c-4c87-8875-7a76f6a34ee5
quzuo-demo 6612d37a-1ab0-4436-ba39-eaeb4c2a29f7
4.2 401 对照:缺失与伪造 token
不带 Authorization 头:
curl -s -D - http://localhost:8080/admin/realms
实测回显:
HTTP/1.1 401 Unauthorized
{"error":"HTTP 401 Unauthorized"}
伪造 token(forged-token-abc)实测回显一致:HTTP/1.1 401 Unauthorized 与 {"error":"HTTP 401 Unauthorized"}。两个 401 的对照说明:Keycloak 对"没有 token"与"token 无效"返回同一错误形态,排查时先确认 Authorization: Bearer 头是否拼接正确(单词 Bearer、一个空格),再检查 token 是否过期。
五、Realm 域 API
5.1 创建 Realm(POST,201)
创建业务域 quzuo-demo:
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST http://localhost:8080/admin/realms \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"realm":"quzuo-demo","enabled":true,"displayName":"Quzuo Demo"}'
实测回显:
HTTP 201
5.2 重复创建的 409 对照
curl -s -D - -o /dev/null -X POST http://localhost:8080/admin/realms \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"realm":"quzuo-demo","enabled":true}'
curl -s -X POST http://localhost:8080/admin/realms \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"realm":"quzuo-demo","enabled":true}'
实测回显:
HTTP/1.1 409 Conflict
{"errorMessage":"Realm quzuo-demo already exists"}
💡 说明:409 响应体带明确的
errorMessage,幂等脚本可以据此判断"已存在"并跳过创建,而不是盲目重试。
六、用户域 API
6.1 创建用户(POST,201)
curl -s -D - -o /dev/null -X POST http://localhost:8080/admin/realms/quzuo-demo/users \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"username":"zhangsan","enabled":true,"email":"zhangsan@quzuo.net","firstName":"San","lastName":"Zhang","emailVerified":true}'
实测回显:
HTTP/1.1 201 Created
Location: http://localhost:8080/admin/realms/quzuo-demo/users/9e863c96-f97b-4d9c-989e-3088a72a8ae0
6.2 用户列表与检索(GET)
curl -s 'http://localhost:8080/admin/realms/quzuo-demo/users?max=20' -H "Authorization: Bearer $AT" \
| python3 -c 'import sys,json;[print(u["id"], u["username"]) for u in json.load(sys.stdin)]'
实测回显:
9e863c96-f97b-4d9c-989e-3088a72a8ae0 zhangsan
精确检索单个用户(exact=true):
curl -s 'http://localhost:8080/admin/realms/quzuo-demo/users?username=lisi&exact=true' \
-H "Authorization: Bearer $AT"
实测回显(输出节选):
[{"id":"c4c12134-1a64-49cb-948a-05c8613a1564","createdTimestamp":1792228937964,"username":"lisi","enabled":true,...}]
6.3 更新用户资料(PUT,204)
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X PUT \
"http://localhost:8080/admin/realms/quzuo-demo/users/$LISI" \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"firstName":"Si","lastName":"Li","email":"lisi@quzuo.net","emailVerified":true,"enabled":true}'
实测回显:
HTTP 204
⚠️ 注意:PUT 更新是全量语义,最好先 GET 目标用户、在返回体上修改后再整体 PUT,否则未提交的字段可能被清空。
6.4 重置密码(PUT reset-password,204)
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X PUT \
"http://localhost:8080/admin/realms/quzuo-demo/users/9e863c96-f97b-4d9c-989e-3088a72a8ae0/reset-password" \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"type":"password","value":"q1w2e3r4","temporary":false}'
实测回显:
HTTP 204
temporary:false 表示密码立即生效、不强制首次登录改密;设为 true 则用户首次登录会被重定向到修改密码页。
6.5 重复用户名的 409 对照
curl -s -X POST http://localhost:8080/admin/realms/quzuo-demo/users \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"username":"zhangsan","enabled":true}'
实测回显:
HTTP/1.1 409 Conflict
{"errorMessage":"User exists with same username"}
6.6 删除用户(DELETE,204 → 复查 404)
curl -s -o /dev/null -w 'DELETE HTTP %{http_code}\n' -X DELETE \
"http://localhost:8080/admin/realms/quzuo-demo/users/$LISI" \
-H "Authorization: Bearer $AT"
curl -s -D - "http://localhost:8080/admin/realms/quzuo-demo/users/$LISI" \
-H "Authorization: Bearer $AT" | grep -iE '^HTTP'
实测回显:
DELETE HTTP 204
HTTP/1.1 404 Not Found
验证结论:删除后复查返回 404,形成"删除成功 → 资源不存在"的完整闭环。
七、客户端域 API
7.1 创建客户端与检索
创建公开客户端 quzuo-app:
curl -s -D - -o /dev/null -X POST http://localhost:8080/admin/realms/quzuo-demo/clients \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"clientId":"quzuo-app","enabled":true,"protocol":"openid-connect","publicClient":true,"standardFlowEnabled":true,"directAccessGrantsEnabled":true,"redirectUris":["http://localhost:8080/callback","http://localhost:3000/callback"],"webOrigins":["+"],"attributes":{"pkce.code.challenge.method":"S256"}}'
实测回显:
HTTP/1.1 201 Created
Location: http://localhost:8080/admin/realms/quzuo-demo/clients/010343a9-ae98-4d69-b445-2d2f1d6aa626
按 clientId 精确检索(返回体中即含系统分配的 UUID):
curl -s 'http://localhost:8080/admin/realms/quzuo-demo/clients?clientId=quzuo-app' \
-H "Authorization: Bearer $AT" \
| python3 -c 'import sys,json;[print(c["id"], c["clientId"]) for c in json.load(sys.stdin)]'
实测回显:
010343a9-ae98-4d69-b445-2d2f1d6aa626 quzuo-app
7.2 创建机密客户端并读取 secret
创建机密客户端 quzuo-service(开启服务账号,供 client_credentials 场景使用):
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST http://localhost:8080/admin/realms/quzuo-demo/clients \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"clientId":"quzuo-service","enabled":true,"protocol":"openid-connect","publicClient":false,"standardFlowEnabled":false,"serviceAccountsEnabled":true,"secret":"qz-svc-secret-2026"}'
实测回显:
HTTP 201
也可以不预设 secret、由系统生成后用专用端点读取(GET .../clients/{uuid}/client-secret):
curl -s http://localhost:8080/admin/realms/quzuo-demo/clients/ba383791-956b-4ce6-89cf-32585f52de7b/client-secret \
-H "Authorization: Bearer $AT"
实测回显:
{"type":"secret","value":"qz-svc-secret-2026"}
7.3 在客户端下创建 client 角色
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST \
http://localhost:8080/admin/realms/quzuo-demo/clients/ba383791-956b-4ce6-89cf-32585f52de7b/roles \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"name":"order-reader"}'
实测回显:
HTTP 201
八、组域 API
8.1 创建组并把用户加入组
curl -s -D - -o /dev/null -X POST http://localhost:8080/admin/realms/quzuo-demo/groups \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' -d '{"name":"dev-team"}'
实测回显:
HTTP/1.1 201 Created
Location: http://localhost:8080/admin/realms/quzuo-demo/groups/c265db21-8f51-422e-b2c6-fb3c22fa68a1
把 zhangsan 加入组(PUT 用户与组的关联,无请求体):
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X PUT \
"http://localhost:8080/admin/realms/quzuo-demo/users/9e863c96-f97b-4d9c-989e-3088a72a8ae0/groups/c265db21-8f51-422e-b2c6-fb3c22fa68a1" \
-H "Authorization: Bearer $AT"
实测回显:
HTTP 204
查询用户所属组:
curl -s 'http://localhost:8080/admin/realms/quzuo-demo/users/9e863c96-f97b-4d9c-989e-3088a72a8ae0/groups' \
-H "Authorization: Bearer $AT"
实测回显:
[
{
"id": "c265db21-8f51-422e-b2c6-fb3c22fa68a1",
"name": "dev-team",
"path": "/dev-team",
"subGroups": []
}
]
九、角色域 API
9.1 创建 realm 角色并分配给用户
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST http://localhost:8080/admin/realms/quzuo-demo/roles \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '{"name":"viewer","description":"只读查看角色"}'
实测回显:
HTTP 201
分配 realm 角色(POST 到用户的 role-mappings/realm,请求体为角色对象数组):
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST \
"http://localhost:8080/admin/realms/quzuo-demo/users/9e863c96-f97b-4d9c-989e-3088a72a8ae0/role-mappings/realm" \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '[{"id":"85cbc330-801f-4c87-ae74-6459fb834a7a","name":"viewer"}]'
实测回显:
HTTP 204
分配 client 角色(注意路径要落在具体客户端 UUID 下):
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -X POST \
"http://localhost:8080/admin/realms/quzuo-demo/users/9e863c96-f97b-4d9c-989e-3088a72a8ae0/role-mappings/clients/ba383791-956b-4ce6-89cf-32585f52de7b" \
-H "Authorization: Bearer $AT" -H 'Content-Type: application/json' \
-d '[{"id":"1f3cabfe-dfe8-44ee-a3f2-2f7164aa32a5","name":"order-reader"}]'
实测回显:
HTTP 204
9.2 核对角色映射
curl -s "http://localhost:8080/admin/realms/quzuo-demo/users/9e863c96-f97b-4d9c-989e-3088a72a8ae0/role-mappings/realm" \
-H "Authorization: Bearer $AT" \
| python3 -c 'import sys,json;[print(r["name"]) for r in json.load(sys.stdin)]'
实测回显:
default-roles-quzuo-demo
viewer
client 角色映射同理查询 role-mappings/clients/<客户端UUID>,实测回显:
order-reader
💡 说明:
default-roles-quzuo-demo是每个域自动生成的默认角色组合,新建用户会自动携带,属正常现象。角色分配后是否出现在 token 里、何时生效,见《Keycloak 核心原理详解》第五章的 token 解码实测。
十、分页/过滤/排序通用约定
列表类端点(users/clients/groups/roles)统一支持以下查询参数(本篇 6.2 已实测 max 与 username/exact):
| 参数 | 含义 | 实测用例 |
|---|---|---|
| first | 起始下标(0 起) | ?first=20&max=20 取第二页 |
| max | 每页条数上限 | ?max=20(本篇实测) |
| username / clientId / search | 前缀过滤 | ?username=lisi&exact=true(本篇实测) |
| exact | 关闭默认的前缀模糊匹配 | exact=true |
| briefRepresentation | 列表返回精简对象(不含冗余字段) | ?briefRepresentation=true |
💡 说明:users 列表默认模糊匹配用户名/邮箱/姓名,精确去重校验时务必带
exact=true,否则lisi会同时命中lisi1之类的前缀重名。
十一、常见问题
Q1:请求返回 401 {"error":"HTTP 401 Unauthorized"}? 先核对三件事:Authorization 头格式(Bearer 后一个空格)、token 是否已过有效期(admin-cli token 默认 1800 秒)、token 取自的域是否正确(管理 token 必须取自 master 域,/realms/master/protocol/openid-connect/token)。
Q2:创建资源返回 409? 见 5.2/6.5 实测,两种高频 409:Realm quzuo-demo already exists 与 User exists with same username。脚本化创建时按 409 幂等跳过即可。
Q3:密码 grant 报 Account is not fully set up? 实测当用户缺少邮箱/姓名等基础资料(或存在未完成的 required action)时,取 token 会被拒绝:
{"error":"invalid_grant","error_description":"Account is not fully set up"}
处理:按 6.3 的方式 PUT 补全 firstName/lastName/email 并将 emailVerified 置 true,或用 Admin 控制台清除该用户的 Required Actions。
Q4:用管理员 token 调业务端点返回 403? 实测 master 域 admin-cli 的 token 调 /realms/master/protocol/openid-connect/userinfo 得到 HTTP 403:userinfo 这类业务端点要求调用方具备对应 scope/角色(如 profile),admin-cli 默认不含。管理 token 只用于 /admin 路径,两者不要混用。
Q5:返回 404 但资源明明存在? 检查三处拼写:域名称大小写(quzuo-demo)、资源 UUID(要用系统分配的 UUID 而不是 clientId/username)、以及 /admin/realms/{realm} 中的 realm 段是否遗漏。本篇 6.6 实测展示了"删除后 404"才是正确的 404 用法。
十二、参考链接
- Keycloak Admin REST API 完整参考(OpenAPI):https://www.keycloak.org/docs-api/latest/rest-api/index.html
- Server Administration Guide(Admin API 与权限模型):https://www.keycloak.org/documentation
- OAuth 2.0 Bearer Token 规范 RFC 6750:https://datatracker.ietf.org/doc/html/rfc6750
- 本专栏相关篇目:《Keycloak 核心原理详解》(token 结构与 OIDC 流程原理)、《Keycloak 配置参数详解》(服务端参数)
- 相关文档:《Vaultwarden 运维手册》《OpenBao 2.7.1 常用 CLI 命令与工具详解》