Skip to content

认证与身份双通道模式篇

目标:让集成方正确选择并实现三种身份形态——纯应用身份(APP_ONLY)、强通道(代表已签发会话的用户)、弱通道(代表一个外部身份),理解 token 生命周期、跨租户规约与双通道常见坑。

不在本篇范围:Console 后台相关能力不在 V1 对接范围(见 API 分区);用户 / 角色 / 部门同步建模(业务身份模式篇);ACL 继承与有效权限计算(见 ACL 与继承模式篇);错误码完整码表(见错误码参考)。

本篇是集成拓扑 §TRUSTED_DELEGATED的纵深展开。概念页讲「集成方代理终端用户」的模型,本篇讲「具体带哪些 header、各自语义、失效码、坑」。先跑通认证最小闭环见入门篇 §认证

三种身份形态总览

V1 的每个请求都先带 Authorization: Bearer <accessToken>(应用身份),再可选叠加委派头表达「以某个终端用户身份调用」。三种形态:

形态含义Authorization 之外附加的 headerACL 解析主体
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(头组合非法)→ 400101010(user token 失效)→ 401101011(跨租户)→ 403。这与入门篇 §HTTP status 统一 200的「业务错统一 HTTP 200」不同(那条针对经服务端统一兜底的业务异常);对这几个委派码,集成方需同时看 HTTP status 与 body.code

一、client credentials:取 access token

V1 通道唯一的认证形态是 client credentials——POST clientId + clientSecretaccessToken

关键事实

  • /v1/auth/token 是 V1 通道唯一可在未持 token 时调用的端点;V1 通道只通过 Authorization: Bearer <token> 鉴权,不要求任何额外的自定义签名或鉴权 header。
  • accessToken不透明字符串(约 32 字符的随机串,不是 JWT)——不要本地 decode / 当 JWT 解析,里面没有可读的过期时间或身份声明。
  • tenantIdclientId 在服务端解码得到,写入响应;后续请求不需要回传 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 通道只识别 Authorization header——不要把 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-SourceX-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 或只带 -Id101009。规避:两头一起带或一起删。
  • ⚠️ 弱通道未授权静默降级:委派授权能力未开启时,请求不会报错,但会按 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,二者无关。

下一步