外观
业务身份模式篇
目标:让集成方理解 atkonbase 内的「业务用户 / 角色 / 部门」三类身份对象——它们如何来自不同
source(CONSOLE / API / DINGTALK / FEISHU / WECOM / SSO / LDAP)、跨 source 有什么硬约束、granteeId怎么解析、当前用户自助身份查询走哪些端点、强通道X-Atk-User-Token与弱通道X-Atk-Act-As-Source*下「当前用户」是谁。不在本篇范围:双通道头怎么带、token 生命周期、跨租户委派规约(见 认证与身份双通道模式篇);ACL 三段式与继承(见 ACL 与继承模式篇);IdP 同步链路 / SSO 主体接入(属平台运维 / IdP 集成范畴,错误码见 IdP/SSO 105*);错误码完整码表(见错误码参考)。
本篇是 集成拓扑 §身份转述 与 领域模型 §ACL · granteeId 的纵深展开。
一、身份三类对象(用前必读)
atkonbase 内的业务身份只有三类对象,统一构成 ACL 的「被授权方(grantee)」:
| 对象 | 业务键 | 解决什么 | 备注 |
|---|---|---|---|
| User(业务用户) | userId | 终端用户在 atkonbase 内的稳定身份 | 与外部 IdP / 集成方业务系统通过 (source, externalSourceId) 映射;环境内稳定,跨环境会重新生成 |
| Role(业务角色) | roleId | 一组授权位的共同载体——把 ACL 授给一个角色,角色成员都能享受 | 角色 ↔ 用户是多对多 |
| Department(部门) | deptId | 组织层级身份;按部门授权时部门下的成员都被覆盖 | 部门 ↔ 用户是多对多;本地部门有层级,IdP 部门按 source 内层级 |
⚠️
granteeId在 ACL 里就是上面三个业务键之一,不是 IdP 侧的 unionId / sub / employeeNo,也不是集成方业务系统的原始用户 / 角色 / 部门主键。
source:身份来源
每个用户 / 角色 / 部门带 source 字段,取值之一:
CONSOLE—— 由租户管理员在 Console 内手动创建API—— 由集成方通过 V1 端点(POST /v1/users/save等)创建DINGTALK/FEISHU/WECOM—— 同步自钉钉 / 飞书 / 企业微信SSO—— 经 SSO 流程接入LDAP—— 同步自 LDAP 目录
集成方约定:
CONSOLE与API是「本地源」——可在 atkonbase 内做完整 CRUD。- 其它 source 是「IdP 同步源」——只读,要改在 IdP 侧改后等同步生效;试图本地修改会返 112005 SOURCE_IMMUTABLE。
二、当前用户自助查询(/v1/users/me/*)
集成方在自己的服务端 / 前端常需「当前用户是谁、有哪些角色 / 部门 / 权限码」——靠 /v1/users/me/*。「当前用户」由双通道 header 当场解析:
- 带强通道
X-Atk-User-Token—— 当前用户 = 该 user token 对应的业务用户 - 带弱通道
X-Atk-Act-As-Source+X-Atk-Act-As-Source-Id—— 当前用户 = 该(source, sourceId)映射到的业务用户 - 不带任何委派头 —— 此时无委派用户(
APP_ONLY),me/*端点不返回业务用户身份(解析不出主体时返 102003 PERMISSION_SESSION_MISSING)
详细形态见 认证与身份双通道模式篇。
基本信息(GET /v1/users/me/get)
bash
curl -X GET 'https://api.atkonbase.example.com/v1/users/me/get' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'X-Atk-User-Token: ${userToken}'响应(UserResultDTO)含 userId / tenantId / tenantCode / displayName / nickname / source / mobile / email / employeeNo / avatar / position / primaryDepartment / departments / roles / status。
tenantCode是tenantId在租户内的稳定别名(一一对应)——集成方服务端拼公开分享 URL 时直接用,避免额外反查tenants/me。displayName是「nickname 优先、回退 username」算出的展示名;UI 直接渲染该字段即可。creatorClientId仅source=API时有值——它是创建该用户的 API Client 凭据 ID(追溯创建来源)。
详情(GET /v1/users/me/getDetail)
返回 UserMeDetailResultDTO,额外携带 permissions 权限码并集字段——语义上是用户持有的所有业务角色权限码的去重合并集。
⚠️
permissions字段目前返回空集合,暂不可用于前端按钮级渲染,请改用/v1/acl/check/batchCheck按资源动态判定;该字段恢复后将在更新日志中说明。
当前用户的角色 / 部门列表(/listRoles · /listDepartments)
GET /v1/users/me/listRoles→UserRolesResultDTO{ roles: RoleRefDTO[] }GET /v1/users/me/listDepartments→UserDepartmentsResultDTO{ primary, all }:primary是主部门、all是归属的全部部门(含主部门)
自助修改资料 / 改密(/updateProfile · /changePassword)
bash
curl -X POST 'https://api.atkonbase.example.com/v1/users/me/updateProfile' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'X-Atk-User-Token: ${userToken}' \
-H 'Content-Type: application/json' \
-d '{ "nickname": "Alice Wang", "mobile": "+8613800000000", "email": "alice@example.com" }'- 只允许修改
nickname/avatar/mobile/email;其它字段(employeeNo/position/departments/roles)不在自助范围。 - ⚠️ 改密对 IdP 源用户不可用:
/v1/users/me/changePassword对 IdP 来源用户拒绝(返 112011 CREDENTIAL_IDP_USER_FORBIDDEN)——他们的凭据由 IdP 侧管理,不在 atkonbase 内。集成方做「改密」入口前判断source,IdP 用户改走 IdP 侧链路。
三、用户、角色、部门 CRUD(按 source 分支)
V1 端点提供对本地源(CONSOLE / API)实体的完整 CRUD;对 IdP 源只读。集成方按 source 分支用法。
业务用户
| 端点 | 用途 | 备注 |
|---|---|---|
POST /v1/users/save | 创建业务用户 | 通过本端点创建的用户 source=API;creatorClientId 由系统自动填充,无需在请求中提供(错误码 112006 不会在 V1 对接中出现) |
POST /v1/users/update | 编辑业务用户 | nickname / mobile / email / employeeNo / avatar / position / primaryDepartmentId / departmentIds / roleIds 等;改 IdP 源用户返 112005 |
POST /v1/users/disable | 禁用业务用户 | query 参数 userId;禁用后该用户在 ACL 解析中被过滤、权限按 0 算(呼应 ACL 与继承模式篇 §二) |
POST /v1/users/setCredentials | 设置 / 重置业务用户账密 | 对 IdP 用户返 112011;对系统用户返 112011 |
POST /v1/users/getPage | 分页查询业务用户 | 过滤位 source / status / keyword(姓名 / 手机 / 邮箱 / 工号模糊) |
GET /v1/users/get?userId=... | 获取业务用户详情 | |
GET /v1/users/listByRole?roleId=... | 按角色查成员 | 返回 UserRefDTO[] |
| `GET /v1/users/listByDepartment?deptId=...&includeChildren=true | false` | 按部门查成员 |
⚠️
primaryDepartmentId必须出现在departmentIds内(112016 PRIMARY_DEPARTMENT_NOT_IN_LIST)——主部门必须同时是用户的归属部门之一。这是平台硬约束。
业务角色
| 端点 | 用途 | 备注 |
|---|---|---|
POST /v1/roles/save | 创建角色 | 通过本端点创建的角色 source=API |
POST /v1/roles/update | 编辑角色 | 改 IdP 源角色返 112005 |
POST /v1/roles/remove?roleId=... | 删除角色 | 角色删除前要先清空成员;详细约束按响应 msg 看 |
POST /v1/roles/addMembers | 批量加入成员 | 允许跨 source 授予(业务角色是「能力载体」、不绑成员 source) |
POST /v1/roles/removeMembers | 批量移除成员 | 同上 |
POST /v1/roles/getPage | 分页查角色 | |
GET /v1/roles/get?roleId=... | 角色详情 | |
GET /v1/roles/listUsers?roleId=... | 列出角色成员 |
⚠️ 业务角色的
addMembers允许跨 source 授予——把 DINGTALK 同步过来的用户加入 CONSOLE 创建的角色是合法的(业务角色作为权限载体,其成员可以来自任意来源)。部门addMembers同此口径(见下表 ⚙️ 部门)。两者的跨 source 约束都在「层级 parentDept」上(必须同 source),而不在「成员关系」上。
部门
| 端点 | 用途 | 备注 |
|---|---|---|
POST /v1/departments/save | 创建本地部门 | parentDeptId 必须与本部门同 source 来源(跨 source 返 112007);同租户约束由更上层处理(跨租户返 112008) |
POST /v1/departments/update | 编辑本地部门 | IdP 源部门返 112005 |
POST /v1/departments/remove?deptId=... | 删除本地部门 | 有子节点返 112009;有成员返 112010。不支持级联,先清子节点 + 转出成员 |
POST /v1/departments/addMembers | 批量加入成员 | 允许跨 source 授予(如把 DINGTALK 同步过来的用户加入 CONSOLE 部门)——与业务角色 addMembers 一致 |
POST /v1/departments/removeMembers | 批量移除成员 | 同上 |
POST /v1/departments/getPage | 分页查部门(支持 source 过滤) | |
GET /v1/departments/get?deptId=... | 部门详情 | |
| `GET /v1/departments/listMembers?deptId=...&includeChildren=true | false` | 列出成员 |
GET /v1/departments/listAncestors?deptId=... | 祖先链(自下而上、不含自身) | |
GET /v1/departments/listDescendants?deptId=... | 所有下级(不含自身) | |
GET /v1/departments/getTree | 按 source 分组的部门树 | 返回 Map<source, DepartmentTreeNodeDTO[]>;每个 source 单独一棵树 |
⚠️ 部门的 source 约束在「层级 parentDept」上,不在「成员关系」上:本地部门的
parentDeptId必须与自身同 source(跨 source 返 112007);但addMembers允许跨 source(如把 DINGTALK 用户加入 CONSOLE 部门)。这与「业务角色 addMembers 跨 source 允许」口径一致——成员归属是「权限 / 组织」业务关系,不破坏 IdP 同步树形结构本身。
四、granteeId 解析路径
集成方做 ACL 授权的最后一步必须把外部身份解析成 atkonbase 内的稳定业务键(userId / roleId / deptId)。典型路径:
4.1 弱通道场景(应用持外部 IdP 身份)
集成方持有 (source, sourceId)(如钉钉 userId),要做「给该用户授权」:
- 应用要么在主体接入时缓存了
(source, sourceId) → userId映射; - 要么调
me/get(带弱通道头)拿当前用户userId; - 再用
userId作granteeId调/v1/acl/set。
(source, sourceId) 映射在 atkonbase 内可能尚未接入——这时 me 端点返 105006 USER_NOT_SYNCED;规避:先确保 IdP 同步链路把对应主体推过来。
4.2 强通道场景(应用持 atkonbase user token)
已有 user token → 调 me/get 直接拿 userId,或在签发 user token 时已知用户身份直接用。
4.3 给角色 / 部门授权
集成方先调 roles/getPage / departments/getPage 按业务名定位 → 拿 roleId / deptId → 作 granteeId 调 ACL set。业务名 / 角色 key 可能在不同环境不同——跨环境对接应用稳定的「角色 key(roleKey)」 / 「部门 key」做映射,请勿将 roleId / deptId 写死在配置中。
⚠️ 跨环境引用 ID 会失效:
userId/roleId/deptId每环境独立生成。生产环境配出来的 ACL 不能复用到测试环境——按业务键(user 的(source, sourceId)/ role 的roleKey/ department 的(source, name)或自定义稳定键)做跨环境引用。
五、source 与 displayName 在 UI 上的边界
集成方做用户 / 部门选择器时常踩的坑:
- 同名跨 source 实体可能并存(CONSOLE 里有「研发部」,DINGTALK 同步过来也有「研发部」)。UI 必须显示 source 标识避免误选(如
研发部 · 钉钉vs研发部 · Console)。 displayName(来自UserResultDTO.displayName)是平台算好的展示名(nickname 优先、回退 username),集成方不要自己拼。getTree端点返回Map<source, ...>的形态就是为「按 source 分树展示」准备的——直接渲染即可。
六、跨租户硬隔离
业务身份按租户隔离:
userId/roleId/deptId租户内唯一,跨租户独立命名空间。- 委派用户的
tenantId必须 == API Client 的tenantId,否则返 101011 CROSS_TENANT_VIOLATION。 - 多租户集成方为每个租户分别申请 API Client 凭据,不可跨租户复用——见 认证与身份双通道模式篇 §跨租户规约。
常见坑
- ⚠️
granteeId传 IdP 侧 ID:把钉钉userId/ SSOsub直接当granteeId,授权落到不存在的主体。规避:先解析成 atkonbase 内userId(弱通道经(source, sourceId)映射、强通道经me/get)。 - ⚠️ 跨环境写死业务键 ID:把生产的
roleId拷到测试。规避:用roleKey/(source, sourceId)等跨环境稳定的业务标识。 - ⚠️ 修改 IdP 源实体:试图本地改 IdP 同步来的用户 / 角色 / 部门。规避:返 112005,去 IdP 侧改后等同步生效;前端按
source渲染只读 UI。 - ⚠️ 本地部门跨 source 挂父节点:试图把 CONSOLE 部门的
parentDeptId指向某个 DINGTALK 同步部门,平台拒绝(返 112007)。规避:父子部门必须同 source;跨 source 的组织聚合靠业务角色 / 上层业务系统编排,不靠部门层级跨源。 - ⚠️
primaryDepartmentId不在departmentIds内:以为主部门独立设置不依赖归属列表。规避:主部门必须同时是归属之一(112016);先把目标部门加入departmentIds,再指primaryDepartmentId。 - ⚠️ 删本地部门没先清空:直接
remove带子部门 / 带成员的部门。规避:先递归处理子节点(112009)、转出成员(112010),再删本部门——不支持级联。 - ⚠️ 以 token 的
mode判当前用户:以为mode != APP_ONLY才能调me/*。规避:mode对 client credentials 恒为 APP_ONLY;当前用户由每请求 header 状态决定——见 认证与身份双通道模式篇 §一。 - ⚠️ 给 IdP 用户改密:UI 上对所有用户都暴露「修改密码」入口。规避:先按
source判分支,IdP 用户改走 IdP 侧链路;本地(CONSOLE / API)用户走users/me/changePassword或管理员侧users/setCredentials。 - ⚠️
displayName二次拼接:业务自己拼「username(nickname)」却与平台口径不一致。规避:直接用UserResultDTO.displayName与createByName字段,平台已统一好。 - ⚠️ 同名跨 source 不区分:UI 用户 / 部门选择器只显示名字。规避:必显
source标签;按 source 分组展示(getTree已是按 source 分组形态)。 - ⚠️ 禁用用户后 ACL 仍生效幻觉:以为禁用只是登录限制,ACL 仍流通。规避:禁用用户在 ACL 解析中被过滤、权限按 0 算(103002 误认为 ACL 配置错),见 认证与身份双通道模式篇 §常见坑。
下一步
- 拿到身份对应的 token / 双通道头怎么带 → 认证与身份双通道模式篇
- 用业务键作
granteeId授权 / 查权限 → ACL 与继承模式篇 - 业务身份相关错误码 → 业务身份 112*、用户 103*、认证 101*
- V1 端点契约 + Try-it → 接口参考