Skip to content

业务身份模式篇

目标:让集成方理解 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 目录

集成方约定:

  • CONSOLEAPI 是「本地源」——可在 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

  • tenantCodetenantId 在租户内的稳定别名(一一对应)——集成方服务端拼公开分享 URL 时直接用,避免额外反查 tenants/me
  • displayName 是「nickname 优先、回退 username」算出的展示名;UI 直接渲染该字段即可。
  • creatorClientIdsource=API 时有值——它是创建该用户的 API Client 凭据 ID(追溯创建来源)。

详情(GET /v1/users/me/getDetail

返回 UserMeDetailResultDTO,额外携带 permissions 权限码并集字段——语义上是用户持有的所有业务角色权限码的去重合并集。

⚠️ permissions 字段目前返回空集合,暂不可用于前端按钮级渲染,请改用 /v1/acl/check / batchCheck 按资源动态判定;该字段恢复后将在更新日志中说明。

当前用户的角色 / 部门列表(/listRoles · /listDepartments

  • GET /v1/users/me/listRolesUserRolesResultDTO{ roles: RoleRefDTO[] }
  • GET /v1/users/me/listDepartmentsUserDepartmentsResultDTO{ 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=APIcreatorClientId 由系统自动填充,无需在请求中提供(错误码 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=truefalse`按部门查成员

⚠️ 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=truefalse`列出成员
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),要做「给该用户授权」:

  1. 应用要么在主体接入时缓存了 (source, sourceId) → userId 映射;
  2. 要么调 me/get(带弱通道头)拿当前用户 userId
  3. 再用 userIdgranteeId/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) 或自定义稳定键)做跨环境引用。

五、sourcedisplayName 在 UI 上的边界

集成方做用户 / 部门选择器时常踩的坑:

  • 同名跨 source 实体可能并存(CONSOLE 里有「研发部」,DINGTALK 同步过来也有「研发部」)。UI 必须显示 source 标识避免误选(如 研发部 · 钉钉 vs 研发部 · Console)。
  • displayName(来自 UserResultDTO.displayName)是平台算好的展示名(nickname 优先、回退 username),集成方不要自己拼。
  • getTree 端点返回 Map<source, ...> 的形态就是为「按 source 分树展示」准备的——直接渲染即可。

六、跨租户硬隔离

业务身份按租户隔离:

常见坑

  • ⚠️ granteeId 传 IdP 侧 ID:把钉钉 userId / SSO sub 直接当 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.displayNamecreateByName 字段,平台已统一好。
  • ⚠️ 同名跨 source 不区分:UI 用户 / 部门选择器只显示名字。规避:必显 source 标签;按 source 分组展示(getTree 已是按 source 分组形态)。
  • ⚠️ 禁用用户后 ACL 仍生效幻觉:以为禁用只是登录限制,ACL 仍流通。规避:禁用用户在 ACL 解析中被过滤、权限按 0 算(103002 误认为 ACL 配置错),见 认证与身份双通道模式篇 §常见坑

下一步