Files
iam-service/docs/SCALAR_GUIDE.md
2026-01-31 13:37:15 +08:00

275 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# IAM Service — Scalar 调用顺序指南v0.1.0
## Authentication认证方式
本服务使用 **JWT Bearer Token**
- 登录成功后拿到 `access_token`
- 后续请求在 Header 中带:
- `Authorization: Bearer <access_token>`
- 租户上下文:
- 保护接口默认从 Token claim 的 `tenant_id` 推导租户
- 可选兼容 `X-Tenant-ID: <uuid>`,若同时提供 Header 与 Token则必须一致否则返回 403
访问令牌JWT`tenant_id/user_id/roles/permissions` 外,还包含:
- `apps`:租户已开通应用列表(如 `["cms","tms"]`
- `apps_version`:租户 enabled_apps 版本号(用于客户端判断是否需要刷新会话)
## OpenAPI/Scalar 可配置参数(环境变量)
为方便本地调试与演示,服务会在生成 OpenAPI 文档时,将部分 Header 参数的示例值按环境变量动态注入(影响 `/scalar` 展示,不影响服务鉴权逻辑):
- `IAM_DOCS_DEFAULT_TENANT_ID`:默认租户 ID用于 `X-Tenant-ID` Header 的 example
- 未设置时默认:`11111111-1111-1111-1111-111111111111`
- 可选强制:`IAM_DOCS_REQUIRE_TENANT_ID=1`(未设置则启动时直接失败)
- `IAM_DOCS_DEFAULT_TOKEN`:默认 Token用于 `Authorization` Header 的 example
- 可填写裸 JWT 或 `Bearer <jwt>`;裸 JWT 会自动补齐 `Bearer `
- 未设置时默认:`Bearer <access_token>`
- 可选强制:`IAM_DOCS_REQUIRE_TOKEN=1`(未设置则启动时直接失败)
## 通用响应结构
成功响应:
```json
{ "code": 0, "message": "Success|Created|Accepted", "data": {}, "trace_id": null }
```
错误响应(示例):
```json
{ "code": 20006, "message": "Missing authorization header", "details": null, "trace_id": null }
```
常见错误码(节选):
- 20006缺少必要 Header如 Authorization
- 20003无权限403
- 20005账号或密码错误
- 30000请求参数错误400
- 30002资源不存在404
- 30003资源冲突409
- 40000请求过于频繁429
- 10001数据库错误500
## Step-by-step可复制流程
### Step -1数据库初始化 / 迁移(开发/测试/生产)
本服务新增了“租户已开通应用enabled_apps”与“平台超级管理员SuperAdmin”能力对应数据库新增表
- `apps`
- `tenant_entitlements`
- `tenant_enabled_apps_history`
推荐使用版本化迁移脚本初始化与升级:
- 执行迁移:`scripts/db/migrate.sh`
- 执行校验:`scripts/db/verify.sh`
- 回滚(按版本):`scripts/db/rollback.sh`
本仓库同时保留开发用的一键重建脚本:`scripts/db/rebuild_iam_db.sh`(会清库重建,不适合生产)。
### Step 0创建租户可选
**POST** `/tenants/register`
- Tag`Tenant`
- Header
- Body
```json
{ "name": "Tenant A", "config": { "theme": { "primary": "#1d4ed8" } } }
```
成功201`data.id` 取出租户 ID
```json
{ "code": 0, "message": "Created", "data": { "id": "<tenant_id>", "name": "Tenant A", "status": "active", "config": {} }, "trace_id": null }
```
下一步依赖:`tenant_id`(用于注册/登录时的 `X-Tenant-ID`)。
### Step 0.1:平台超级管理员(可选,但推荐先完成)
数据库已内置平台租户Platform tenant
- `tenant_id = 00000000-0000-0000-0000-000000000001`
在平台租户下注册首个用户,将自动获得平台级权限(用于管理各租户 enabled_apps
**POST** `/auth/register`
- Tag`Auth`
- Header`X-Tenant-ID: 00000000-0000-0000-0000-000000000001`
- Body
```json
{ "email": "superadmin@example.com", "password": "securePassword123" }
```
### Step 1注册用户
**POST** `/auth/register`
- Tag`Auth`
- 必需 Header`X-Tenant-ID: <tenant_id>`
- Body
```json
{ "email": "user@example.com", "password": "securePassword123" }
```
成功201`data.id` 取出 `user_id`(后续可用于用户管理接口):
```json
{ "code": 0, "message": "Created", "data": { "id": "<user_id>", "email": "user@example.com" }, "trace_id": null }
```
### Step 2登录获取访问令牌Authentication 入口)
**POST** `/auth/login`
- Tag`Auth`
- 必需 Header`X-Tenant-ID: <tenant_id>`
- Body
```json
{ "email": "user@example.com", "password": "securePassword123" }
```
成功200`data.access_token` 取出访问令牌:
```json
{ "code": 0, "message": "Success", "data": { "access_token": "<jwt>", "refresh_token": "<opaque>", "token_type": "Bearer", "expires_in": 900 }, "trace_id": null }
```
下一步依赖:`access_token`
### Step 3获取当前租户信息Tenant
**GET** `/tenants/me`
- Tag`Tenant`
- 必需 Header`Authorization: Bearer <access_token>`
- 可选 Header`X-Tenant-ID: <tenant_id>`(如提供必须与 token tenant_id 一致)
成功200
```json
{ "code": 0, "message": "Success", "data": { "id": "<tenant_id>", "name": "Tenant A", "status": "active", "config": {} }, "trace_id": null }
```
### Step 4查看当前用户权限Me
**GET** `/me/permissions`
- Tag`Me`
- 必需 Header`Authorization: Bearer <access_token>`
成功200
```json
{ "code": 0, "message": "Success", "data": ["tenant:read","tenant:write"], "trace_id": null }
```
下一步依赖:确认具备目标权限(例如 `user:read` / `role:read`)。
### Step 4.1平台层设置租户已开通应用SuperAdmin
该能力仅允许拥有平台级权限的用户调用:
- `iam:tenant:enabled_apps:read`
- `iam:tenant:enabled_apps:write`
#### 4.1.1 查询某租户 enabled_apps
**GET** `/platform/tenants/{tenant_id}/enabled-apps`
- Tag`Tenant`
- Header`Authorization: Bearer <access_token>`(平台租户下登录得到的 token
#### 4.1.2 设置某租户 enabled_apps全量覆盖幂等
**PUT** `/platform/tenants/{tenant_id}/enabled-apps`
- Tag`Tenant`
- Header`Authorization: Bearer <access_token>`
- Body
```json
{ "enabled_apps": ["cms", "tms"], "expected_version": 0 }
```
说明:
- `expected_version` 可选,用于并发控制;不匹配会返回 409。
- 登录签发 token 时会自动把 `apps/apps_version` 注入到 JWT并对 `permissions` 按 enabled_apps 过滤。
- `enabled_apps` 必须是“应用注册表 apps 表”中存在且 `status=active` 的应用 ID若传入未知/已下线应用(例如 `dms`),接口会返回 400。
enabled_apps 维护建议:
- 新增应用:通过数据库迁移向 `apps(id,name,description,status)` 插入一行(`id` 推荐全小写短标识,如 `cms` / `tms`)。
- 下线应用:将 `apps.status` 置为非 `active`(例如 `disabled`),之后将无法再被设置进任何租户的 enabled_apps。
- 查询可用应用(示例 SQL`SELECT id, name, status FROM apps ORDER BY id;`
### Step 5列出用户User
**GET** `/users?page=1&page_size=20`
- Tag`User`
- 必需 Header`Authorization: Bearer <access_token>`
- 分页规则:
- `page` 默认 1必须 >= 1
- `page_size` 默认 20范围 1..=200
成功200
```json
{ "code": 0, "message": "Success", "data": [{ "id": "<user_id>", "email": "user@example.com" }], "trace_id": null }
```
### Step 6列出角色Role
**GET** `/roles`
- Tag`Role`
- 必需 Header`Authorization: Bearer <access_token>`
成功200
```json
{ "code": 0, "message": "Success", "data": [{ "id": "<role_id>", "name": "Admin", "description": "..." }], "trace_id": null }
```
### Step 7用户-角色绑定User
用户注册后默认无角色;通常由具备 `user:write` 的管理员进行角色分配。
#### 7.1 查询用户角色列表(需要 user:read
**GET** `/users/{id}/roles`
- Tag`User`
- Header`Authorization: Bearer <access_token>`
#### 7.2 设置用户角色(全量覆盖,幂等;需要 user:write
**PUT** `/users/{id}/roles`
- Tag`User`
- Header`Authorization: Bearer <access_token>`
- Body
```json
{ "role_ids": ["<role_id_1>", "<role_id_2>"] }
```
说明:
- `role_ids` 必须全部属于当前租户,否则返回 400。
## 限流说明Auth
- `/auth/login`:约 2 req/sburst 10同一 IP
- `/auth/register`:约 1 req/sburst 5同一 IP
- 触发后返回HTTP 429 + `code=40000`