Skip to content

上游身份接入与 User Token 桥接模式篇

目标:让自研业务系统把 atkonbase 当作「身份中枢」接入——业务系统不自建用户体系,用户 / 角色 / 部门 / 权限都在 atkonbase 内管理;业务系统只持「当前用户信息」,并以代表该用户的 User Token 调 V1 API,ACL 由 atkonbase 按用户落地。本篇讲完整闭环:上游登录取凭证 → 换取 User Token → 持 token 调 V1 API → 续期 / 登出。

不在本篇范围:上游 IdP(钉钉 / OIDC / OAuth2)在 atkonbase 内的配置由管理员在 Console 完成,不属 V1 对接面;三种身份形态的 header 级语义见认证与身份双通道模式篇;ACL 与有效权限计算见 ACL 与继承模式篇

本篇是认证与身份双通道的上游延伸:认证篇讲「持 User Token 后怎么带 header 调用」,本篇讲「User Token 从哪来」——业务系统如何用上游登录凭证换到它。

适用形态

典型部署是 atkonbase + 自研业务系统打包交付客户

  • 客户上游已有身份系统(钉钉 / 标准 OIDC / 纯 OAuth2 社交登录)。
  • atkonbase 上接上游身份、下供业务系统消费;业务系统不复制一套用户表。
  • 终端用户经上游登录后,业务系统拿到「代表该用户」的 atkonbase User Token,之后所有内容操作(上传 / 检索 / ACL 检查)都以该用户身份调 V1,权限按用户在 atkonbase 内的角色 / 部门 / ACL 落地。

端到端流程总览

text
①上游登录            ②换 User Token              ③持 token 调 V1            ④维持会话
终端用户在上游 IdP  →  业务后端 POST            →  业务后端以 User Token   →  续期 refresh /
完成登录,业务侧      /v1/auth/ssoExchange         走强通道调 V1 API           主动 logout /
拿到上游凭证          换得 User Token              (ACL 按该用户解析)         禁用即时失效
(authCode / code)   + refreshToken
步骤谁来做关键产物
① 上游登录取凭证业务前端 + 上游 IdP上游凭证(钉钉免登 authCode / OIDC / OAuth2 浏览器授权码)
② 换取 User Token业务后端User Token(accessToken)+ refreshToken
③ 持 token 调 V1业务后端各 V1 接口的业务结果(以该用户身份)
④ 维持会话业务后端续期后的新 User Token / 登出

⚠️ ②③④ 全部发生在业务系统后端。User Token 代表终端用户身份,是强通道凭证——只存后端、不下发浏览器(见下「安全约定」)。

前置:上游 IdP 已在 atkonbase 配置

换取 User Token 前,目标租户须已在 atkonbase Console 配置好对应协议的上游 IdP(钉钉 / OIDC / OAuth2),并完成首登建档策略。这一步由管理员完成,不在 V1 对接面;业务系统侧只需知道该租户用的是哪种上游协议,以便在第②步传对 source

一、上游登录取凭证

终端用户在上游完成登录,业务侧拿到一个一次性上游凭证。凭证形态随上游协议不同:

上游协议source 取值凭证(credential)从哪来
钉钉(容器免登)dingtalk钉钉客户端内前端调用免登 API 拿到的 authCode
标准 OIDCoidc浏览器走授权码流程,IdP 回调业务系统时带的 code
纯 OAuth2(GitHub / 微信 / QQ 等)oauth2同上,浏览器授权码流程回调带的 code

要点:

  • 浏览器授权码流程(OIDC / OAuth2):业务前端把用户重定向到上游 IdP 授权页,用户授权后 IdP 重定向回业务系统的回调地址并带 code;业务前端把 code 交给业务后端,由后端完成第②步换取——code 不要在前端直接换 token。
  • 钉钉免登:前端在钉钉容器内拿到 authCode 后同样交给业务后端。
  • 上游凭证都是一次性、短时效的:拿到后尽快在后端换取,不要缓存复用。

二、换取 User Token

业务后端持 client 身份(clientId + clientSecret 换得的 access token),提交上游凭证换取代表该用户的 User Token。租户由 client 上下文绑定,请求不接受、不信任前端传入的租户

curl

bash
curl -X POST 'https://atkonbase.example.com/api/v1/auth/ssoExchange' \
  -H 'Authorization: Bearer ${clientAccessToken}' \
  -H 'Content-Type: application/json' \
  -d '{
    "source": "oauth2",
    "credential": "${upstreamAuthorizationCode}"
  }'

期望响应(关键字段)

json
{
  "code": 0,
  "data": {
    "accessToken": "u_3f8b5e9c2f8b5e9c1f8b5e9c2f8b5e9c",
    "refreshToken": "r_1f8b5e9c2f8b5e9c1f8b5e9c2f8b5e9c",
    "expiresIn": 7200,
    "mode": "FIRST_PARTY",
    "userId": "U1000042",
    "userName": "张三",
    "tenantId": "T1000001"
  }
}
  • accessToken —— 即 User Token,后续作强通道凭证放进 X-Atk-User-Token(见第三步)。与所有 V1 token 一样是不透明字符串,不要本地当 JWT 解析。
  • refreshToken —— 续期凭证,业务后端持有以维持长会话(见第四步)。
  • userId / userName —— 该 User Token 代表的 atkonbase 内部用户;userId 即落到 ACL 的主体。

SDK 封装为一行 client.exchangeUserToken(source, credential),返回同款结构(accessToken 即 User Token)。各语言用法与照搬即跑的代码见 SDK 文档

三、持 User Token 调 V1 API

拿到 User Token 后,业务后端以强通道调 V1:在 client 的 Authorization 之外附加 X-Atk-User-Token,ACL 即按该用户解析(强通道 header 级语义见认证篇 §强通道)。

解析当前用户

业务系统通常先要拿到「当前用户是谁」——profile、所属部门、角色、权限码。带 User Token 调当前用户视图端点 GET /v1/users/me/getDetail

bash
curl -X GET 'https://atkonbase.example.com/api/v1/users/me/getDetail' \
  -H 'Authorization: Bearer ${clientAccessToken}' \
  -H 'X-Atk-User-Token: ${userToken}'

返回该用户的 userId / nickname / primaryDepartment(主部门)/ departments(所有部门)/ roles(所有角色)/ permissions(权限码并集)。之后任何 V1 内容接口同样带 X-Atk-User-Token 即以该用户身份生效、ACL 按其解析。

SDK 封装为 client.actAsToken(userToken).currentUser() 解析当前用户、client.actAsToken(userToken).api(...) 调任一 V1 接口;与既有弱通道 actAsSource(...) 形态一致。代码见 SDK 文档

User Token 失效(过期 / 已登出 / 已被踢出 / 用户被禁用)统一返业务码 101010(HTTP 401),不区分细节。处理方式一致:让终端用户重新走上游登录取新 User Token。强通道还有独立于 expiresIn 的「活跃超时」,细节见认证篇 §强通道

四、维持会话:续期 / 登出 / 禁用即时失效

续期

User Token 临近过期时,用换取时一并签发的 refreshToken 续期,得到新的 User Token:

bash
curl -X POST 'https://atkonbase.example.com/api/v1/auth/refresh' \
  -H 'Authorization: Bearer ${clientAccessToken}' \
  -H 'Content-Type: application/json' \
  -d '{ "refreshToken": "${refreshToken}" }'

返回结构与换取时一致(新的 accessToken + refreshToken);按业务策略用新值替换后端持有的旧值。refreshToken 失效 / 过期时续期返业务错误,回退到「引导用户重新走上游登录」。SDK 封装为 client.refreshUserToken(refreshToken)

主动登出

终端用户登出业务系统时,按 token 值失效对应的 User Token。登出对象是请求体携带的 User Token——不影响调用方持有的 client 登录态(client token 在 Authorization 头,不是登出对象):

bash
curl -X POST 'https://atkonbase.example.com/api/v1/auth/logout' \
  -H 'Authorization: Bearer ${clientAccessToken}' \
  -H 'Content-Type: application/json' \
  -d '{ "userToken": "${userToken}" }'

SDK 封装为 client.logoutUserToken(userToken)

用户被禁用即时失效

管理员在 atkonbase 内禁用某用户后,其已签发的 User Token 在下一次强通道调用时即时失效、请求被拒(统一返 101010)。业务系统无需轮询用户状态——遇 101010 即按「重新登录」处理即可。

安全约定

⚠️ User Token 只存业务系统后端,永不下发浏览器。

  • User Token 是代表终端用户的强通道凭证:谁持有它,谁就能以该用户身份调用 atkonbase。把它放到前端(localStorage / cookie / 内存)等于把用户的内容权限暴露给浏览器环境。
  • 正确分工:业务前端只负责把一次性上游凭证(authCode / 授权码)交给业务后端;换取、持有、续期、登出 User Token 全在后端。前端要展示「我的文档 / 我的部门」等数据时,由业务后端以 User Token 代调 V1、把结果转发给前端,而不是把 token 交给前端自己调。
  • clientSecret 同理——只存后端,不进任何前端产物。

常见坑

  • ⚠️ 把 User Token / code 交给前端换 token:上游 code 应由后端换,User Token 应由后端持有。规避:前端只传一次性上游凭证,其余全在后端。
  • ⚠️ 请求体里塞 tenantCode 想指定租户/v1/auth/ssoExchange 租户由 client 上下文绑定,请求体不接受租户主张(避免越权)。规避:为每个租户分别申请 client 凭据。
  • ⚠️ source 与租户 IdP 协议不匹配source 必须与该租户在 Console 配置的上游协议一致(dingtalk / oidc / oauth2)。规避:对接前与管理员确认该租户的上游协议。
  • ⚠️ 缓存复用上游凭证:authCode / 授权码是一次性短时效的,复用必失败。规避:拿到即换,不缓存。
  • ⚠️ 本地计时器判 User Token 是否有效:活跃超时由服务端判定,本地推算会误判。规避:以服务端返回的 101010 为准,遇到即重新登录。

下一步