外观
上游身份接入与 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 |
| 标准 OIDC | oidc | 浏览器走授权码流程,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 为准,遇到即重新登录。
下一步
- 三种身份形态的 header 级语义与失效码 → 认证与身份双通道模式篇
- 拿到用户身份后做授权与权限检查 → ACL 与继承模式篇
- SDK 完整用法(认证形态、错误处理、错误码常量)→ TypeScript SDK / Java SDK
- 换取 / 续期 / 上游验票相关错误码 → 认证 101*、IdP/SSO 105*