外观
认证与身份双通道模式篇
目标:让集成方正确选择并实现三种身份形态——纯应用身份(APP_ONLY)、强通道(代表已签发会话的用户)、弱通道(代表一个外部身份),理解 token 生命周期、跨租户规约与双通道常见坑。
不在本篇范围:Console 后台相关能力不在 V1 对接范围(见 API 分区);用户 / 角色 / 部门同步建模(业务身份模式篇);ACL 继承与有效权限计算(见 ACL 与继承模式篇);错误码完整码表(见错误码参考)。
本篇是集成拓扑 §TRUSTED_DELEGATED的纵深展开。概念页讲「集成方代理终端用户」的模型,本篇讲「具体带哪些 header、各自语义、失效码、坑」。先跑通认证最小闭环见入门篇 §认证。
三种身份形态总览
V1 的每个请求都先带 Authorization: Bearer <accessToken>(应用身份),再可选叠加委派头表达「以某个终端用户身份调用」。三种形态:
| 形态 | 含义 | 在 Authorization 之外附加的 header | ACL 解析主体 |
|---|---|---|---|
| APP_ONLY | 纯应用身份,无委派用户 | 无 | API Client 自身 |
| 强通道 | 应用代表一个已签发会话的用户 | X-Atk-User-Token: <user token> | 该用户 |
| 弱通道 | 应用主张代表一个外部系统的身份 | X-Atk-Act-As-Source + X-Atk-Act-As-Source-Id(成对) | 该外部身份解析出的 atkonbase 用户 |
⚠️ 强通道与弱通道互斥,一次请求只能用其一(同时带两者 → 101008)。身份形态是每请求的 header 状态,由每次请求的 header 决定——它不等同于 token 响应里的
mode字段(见下)。
⚠️ 委派头错误带真实 HTTP status:双通道头的解析失败由服务端在请求进入业务层前短路返回,带真实 HTTP 状态码——
101008/101009(头组合非法)→ 400、101010(user token 失效)→ 401、101011(跨租户)→ 403。这与入门篇 §HTTP status 统一 200的「业务错统一 HTTP 200」不同(那条针对经服务端统一兜底的业务异常);对这几个委派码,集成方需同时看 HTTP status 与body.code。
一、client credentials:取 access token
V1 通道唯一的认证形态是 client credentials——POST clientId + clientSecret 换 accessToken。
关键事实
/v1/auth/token是 V1 通道唯一可在未持 token 时调用的端点;V1 通道只通过Authorization: Bearer <token>鉴权,不要求任何额外的自定义签名或鉴权 header。accessToken是不透明字符串(约 32 字符的随机串,不是 JWT)——不要本地 decode / 当 JWT 解析,里面没有可读的过期时间或身份声明。tenantId由clientId在服务端解码得到,写入响应;后续请求不需要回传tenantId。- ⚠️ token 响应的
mode对 client credentials 恒为APP_ONLY。强 / 弱身份是每请求 header 状态,由每次请求的 header 决定,不反映在 token 的mode字段里——不要据mode判断「这个 token 是不是带用户身份」。
curl 示例
bash
curl -X POST 'https://api.atkonbase.example.com/v1/auth/token' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "${clientId}",
"clientSecret": "${clientSecret}"
}'期望响应(关键字段)
json
{
"code": 0,
"data": {
"accessToken": "bcb1c7e5fd9b4d8f8e3c6a1d2f8b5e9c",
"expiresIn": 7200,
"refreshToken": "1f8b5e9c2f8b5e9c1f8b5e9c2f8b5e9c",
"tenantId": "T1000001",
"mode": "APP_ONLY",
"scopes": ["client:storage:file:upload", "client:storage:acl:write"]
}
}expiresIn—— 单位秒;过期判断只看本字段(不可从accessToken本身解析)。scopes—— 该 token 的能力清单;端点 scope 不足时返 102002 / 106009。- 凭证错误返 101002,Client 被禁用返 101003。
⚠️ 后续请求统一带
Authorization: Bearer <accessToken>。V1 通道只识别Authorizationheader——不要把 token 放进其它自定义 token header,那样不会被识别。
二、强通道:X-Atk-User-Token
应用已经为某个终端用户签发了 atkonbase 用户会话(系统自签的用户会话 token),且希望让本次调用「以该用户身份」生效——ACL 走该用户、操作归属到该用户。此时在 Authorization 之外附加 X-Atk-User-Token。
curl 示例
bash
curl -X POST 'https://api.atkonbase.example.com/v1/acl/check' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'X-Atk-User-Token: ${userToken}' \
-H 'Content-Type: application/json' \
-d '{
"resource": { "resourceType": "DOCUMENT", "resourceId": "${docId}" },
"grantees": [{ "granteeType": "USER", "granteeId": "${userId}" }],
"permission": "READ"
}'关键事实
- 何时用:终端用户已在 atkonbase 内有会话(例如此前经 SSO / 强登录拿到 user token),需要让权限检查、操作归属落到该用户。
userToken失效统一返业务码 101010(HTTP 401),且不区分细节(过期 / 已登出 / 已被踢出 / 签名失败都是同一码)。处理方式都一样:让该终端用户重新登录拿新 user token。- ⚠️ 活跃超时独立于
expiresIn:user token 除绝对过期外,还有「活跃超时」(一段时间无调用即失效)。长时间空闲后即便未到绝对过期也可能返 101010;不能用本地计时器替代服务端判定,遇 101010 即重新登录。
三、弱通道:X-Atk-Act-As-Source + X-Atk-Act-As-Source-Id
应用持有外部系统(钉钉 / 飞书 / 企业微信 / SSO 等)里的用户身份,但没有该用户的 atkonbase user token。此时用弱通道主张「我代表这个外部身份」,atkonbase 经映射解析出对应的内部用户。
curl 示例
bash
curl -X POST 'https://api.atkonbase.example.com/v1/acl/check' \
-H 'Authorization: Bearer ${accessToken}' \
-H 'X-Atk-Act-As-Source: DINGTALK' \
-H 'X-Atk-Act-As-Source-Id: ${dingtalkUserId}' \
-H 'Content-Type: application/json' \
-d '{
"resource": { "resourceType": "DOCUMENT", "resourceId": "${docId}" },
"grantees": [{ "granteeType": "USER", "granteeId": "${userId}" }],
"permission": "READ"
}'关键事实
- 两头成对:
X-Atk-Act-As-Source与X-Atk-Act-As-Source-Id必须同时出现,缺一返 101009。 - 前置条件 + 静默降级:弱通道需为该 API Client 启用委派授权能力;如需开启请联系 atkonbase 服务方。⚠️ 未开启时不报错——请求静默降级为 APP_ONLY,权限按 API Client 自身解析。自检方式:用一个该用户无权、但 API Client 有权的资源做
acl/check,若返回allowed=true说明委派没生效、降级成了 APP_ONLY。 source取值对应身份来源枚举:DINGTALK/FEISHU/WECOM/SSO/LDAP。sourceId是来源系统里的稳定主体键(如钉钉 userId、飞书 open_id)——来源系统侧不变的那个标识,不是显示名、不是手机号。- ⚠️ 该外部身份必须已在 atkonbase 内完成主体同步(建立
(tenantId, source, sourceId) → 内部 userId映射)。未完成同步则返 105006;映射缺失还可能表现为会话无主体集 102003。
granteeId 解析与稳定性
无论强 / 弱通道,落到 ACL 的主体都是 atkonbase 内部 userId(呼应领域模型 §ACL):
granteeId= atkonbase 内部用户 ID,不是 IdP 侧的 unionId / sub / employeeNo,也不是集成方业务系统的原始用户主键。- 弱通道经
(tenantId, source, sourceId)查映射表解析出userId:三元组在来源系统侧稳定,但切换 atkonbase 环境时userId会变(每环境独立生成);跨环境对接请以(source, sourceId)为锚,不要把userId写死。
跨租户规约
- 委派用户的
tenantId必须 == API Client 的tenantId,否则返 101011。atkonbase 默认禁止跨租户委派。 - 多租户集成方:为每个租户分别申请 API Client 凭据,不可跨租户复用一套
clientId/clientSecret。
token 生命周期
text
签发(POST /v1/auth/token)→ 携带调用(Authorization: Bearer)→ 过期(expiresIn 秒后)→ 重取- 遇 101001 立即重取:access token 过期 / 被吊销时返 101001,重新走 client credentials 取新 token(
refreshToken刷新失败同样回退到重新取 token)。 - 长连接定期刷新:长时间运行的集成进程应在
expiresIn到期前主动刷新,避免请求落在过期窗口。 - 强通道的 user token 是另一套生命周期(见上「活跃超时」),与 access token 的
expiresIn互不替代。
常见坑
- ⚠️ 双通道混用:同请求既带
X-Atk-User-Token又带X-Atk-Act-As-Source*→ 101008。规避:按场景二选一。 - ⚠️ 弱通道双头不成对:只带
X-Atk-Act-As-Source或只带-Id→ 101009。规避:两头一起带或一起删。 - ⚠️ 弱通道未授权静默降级:委派授权能力未开启时,请求不会报错,但会按 APP_ONLY 解析权限。规避:用「用户无权 / Client 有权」的资源自检;上线前确认委派授权能力已开启。
- ⚠️ 弱通道用户未完成主体同步:
(source, sourceId)在 atkonbase 内无映射 → 105006。规避:先确保主体同步建立映射,再走弱通道。 - ⚠️ 跨租户被拒:委派用户与 Client 不同租户 → 101011。规避:按租户分别持凭据。
- ⚠️ 活跃超时本地误判:以本地计时器推算 user token 仍有效、却已被服务端活跃超时判失效。规避:以服务端返回的 101010 为准,遇到即重新登录,不本地缓存「有效」结论。
- ⚠️ token 当 JWT 解析:尝试 base64 decode
accessToken读 claims / 过期时间。规避:token 不透明,过期只看expiresIn,身份信息不在 token 里。 - ⚠️ 据
mode判委派:以 token 的mode == APP_ONLY推断「没带用户身份」。规避:身份形态是每请求 header 状态,mode对 client credentials 恒为 APP_ONLY,二者无关。
下一步
- 拿到身份后做授权与权限检查 → ACL 与继承模式篇
- 认证相关错误码含义 / 触发 / 建议 → 认证 101*、权限 102*、API Client 106*、IdP/SSO 105*
- 用 SDK 配置三种认证形态 → SDK 总览