外观
Identity Broker 授权码流接入模式篇
目标:让自研业务系统把登录这件事整体委托给 ATKONBASE——业务系统不自建用户体系、也不自接上游身份源,只用标准 OAuth2 授权码流把终端用户重定向到 ATKONBASE 托管登录页,认证完成后用授权码兑换代表该用户的访问令牌(User Token),之后照常以该用户身份调 V1 API。本篇讲完整闭环:发起授权 → 托管登录页认证 → 授权码回跳 → 兑换令牌 → 单点登录 / 登出。
不在本篇范围:拿到 User Token 之后怎么带 header 调 V1、令牌续期 / 失效语义见认证与身份双通道模式篇与上游身份接入与 User Token 桥接模式篇;ACL 与有效权限计算见 ACL 与继承模式篇。
本篇与上游身份接入与 User Token 桥接是两种相反的接入姿势,按「上游身份源在谁那里对接」二选一:
| 上游身份接入(桥接篇) | Identity Broker 授权码流(本篇) | |
|---|---|---|
| 谁对接上游身份源 | 业务系统自己对接上游 IdP,拿到上游凭证后调 /v1/auth/ssoExchange 换令牌 | 业务系统不接触任何上游身份源,登录整体交给 ATKONBASE 托管登录页 |
| 业务系统要写的登录代码 | 上游 IdP 的授权 / 回调 / 取凭证 | 一个发起授权的跳转 + 一个回调兑换,约十来行 |
| ATKONBASE 的角色 | 消费上游身份的一方 | 业务系统的登录中枢(签发授权码与令牌) |
两者产出的 User Token 完全一致、后续调用方式相同;区别只在「令牌从哪来」。
适用形态
典型部署是 ATKONBASE + 一个或多个自研业务系统打包交付客户:
- 业务系统不想自建账号体系,也不想各自对接客户的上游 SSO。
- 把登录页、账密校验、上游 SSO 同屏、失败保护都交给 ATKONBASE 托管。
- 同一租户下接入多个业务系统时,终端用户登录一次即可在这些业务系统间免重复登录(单点登录)。
端到端流程总览
text
①发起授权 ②托管登录页认证 ③授权码回跳 ④兑换令牌
业务后端拼授权 URL,把 → 终端用户在 ATKONBASE → 平台带 code + state → 业务后端 POST
state / codeVerifier 托管登录页完成认证 302 回跳业务系统的 /oauth/token
暂存会话,302 浏览器 (账密 / SSO 同屏) 回调地址 换得 User Token
+ refreshToken| 步骤 | 谁来做 | 关键产物 |
|---|---|---|
| ① 发起授权 | 业务后端 | 授权 URL(带 state + PKCE code_challenge);state / code_verifier 暂存会话 |
| ② 托管登录页认证 | 终端用户 + ATKONBASE | 平台完成认证(业务系统无感) |
| ③ 授权码回跳 | ATKONBASE → 业务系统回调地址 | 一次性授权码 code + 原样回传的 state |
| ④ 兑换令牌 | 业务后端 | User Token(accessToken)+ refreshToken |
⚠️ ①④ 在业务系统后端完成;
clientSecret/code_verifier/state/ User Token 都是后端机密,不下发浏览器(见下「安全约定」)。
前置:登记回调地址
发起授权前,业务系统的 API Client 须先登记一个授权码流回调地址(redirect_uri)。回调地址按精确匹配校验——协议、主机、路径、查询都要与发起授权时携带的 redirect_uri 完全一致,不支持前缀或通配。这一步由管理员在 Console 为对应 Client 完成。
- 一个 Client 对应一个回调落点。多环境(dev / prod)或多入口请各自登记独立的 Client,不要试图让一个 Client 复用多个回调地址。
- 回调地址可随登录页品牌(logo / 标题)一并配置,使终端用户在登录页看到的是接入业务系统自己的品牌。
一、发起授权
业务后端为本次登录生成 state(CSRF 令牌)与 PKCE(code_verifier 随机串、code_challenge = base64url(SHA-256(code_verifier))),拼出授权 URL,把 state 与 code_verifier 暂存到当前用户会话,再把浏览器 302 到该 URL。
text
GET https://atkonbase.example.com/api/oauth/authorize
?client_id=${clientId}
&redirect_uri=https%3A%2F%2Fyour-app.example.com%2Fcallback
&state=${state}
&code_challenge=${codeChallenge}
&code_challenge_method=S256code_challenge_method=S256必须显式携带:缺省时平台按plain原文比对,而这里发的是 SHA-256 摘要,漏带会导致后续兑换一律失败。redirect_uri须与已登记的回调地址逐字一致(精确匹配)。state/code_verifier每次登录新生成、绑定当前会话,回调时取回比对。
SDK 把这一步收成一个原语:
oauth.buildAuthorizeUrl(redirectUri)自动生成state与 PKCE 并返回可直接 302 的授权 URL(code_verifier/state一并返回供暂存)。代码见 SDK 文档。
二、托管登录页认证与回跳
浏览器落到 ATKONBASE 托管登录页后,认证全程由平台承担,业务系统无感:
- 登录方式:账密登录,以及该租户已配置的上游 SSO(同屏可选)。
- 白标:登录页呈现接入业务系统配置的 logo 与标题;未配置时回退平台默认品牌。
- 失败保护:同一账号短时间内多次输错密码后,平台对其登录尝试临时限流;达阈值后登录页要求输入图形验证码方可继续,验证码可点击刷新、一次有效。正常登录不受影响,限流随时间自动恢复、不锁死账号。
认证通过后,平台带一次性授权码回跳业务系统登记的回调地址:
text
302 https://your-app.example.com/callback?code=${code}&state=${state}业务后端在回调路由里先核对回传的 state 与会话暂存值一致(防 CSRF),再进入第三步兑换。
三、用授权码兑换用户令牌
业务后端用授权码兑换代表该用户的 User Token。凭据在 JSON body 提交(不是 Authorization 头):
bash
curl -X POST 'https://atkonbase.example.com/api/oauth/token' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "${clientId}",
"clientSecret": "${clientSecret}",
"code": "${code}",
"redirectUri": "https://your-app.example.com/callback",
"codeVerifier": "${codeVerifier}"
}'期望响应(关键字段)
json
{
"code": 0,
"data": {
"accessToken": "u_3f8b5e9c2f8b5e9c1f8b5e9c2f8b5e9c",
"refreshToken": "r_1f8b5e9c2f8b5e9c1f8b5e9c2f8b5e9c",
"expiresIn": 7200,
"userId": "U1000042",
"userName": "张三",
"tenantId": "T1000001"
}
}accessToken—— 即 User Token,与桥接篇换得的令牌完全同款;后续作强通道凭证放进X-Atk-User-Token,以该用户身份调 V1(怎么带 header、怎么解析当前用户见上游身份接入篇 §三)。refreshToken—— 续期凭证,业务后端持有以维持长会话(续期 / 登出 / 禁用即时失效与桥接篇一致,见桥接篇 §四)。redirectUri须与第一步发起授权时逐字一致,否则兑换被拒。code一次性消费、分钟级失效——拿到尽快兑换,不要缓存或重试同一个code。
SDK 把这一步收成
oauth.exchangeCode(...):内部先校验回传state与暂存state一致(不一致即拒绝、不发请求),再兑换并返回与上面同款的结构。业务方无需自己写 state 比对与 PKCE 回传。代码见 SDK 文档。
四、单点登录与单点登出
单点登录(同租户免重登)
终端用户经授权码流登录过某个接入业务系统后,浏览器再发起同一租户下其它业务系统的授权请求时,平台跳过登录页、直接完成授权回跳,无需重新输入账密或重走上游 SSO:
- 免登仅在同一租户内共享;跨租户仍需重新登录。
- 每个业务系统仍各自兑换、各自获得独立的访问凭证,互不影响。
- 登录会话带绝对过期与闲置超时治理:长时间不活动后自动失效,活跃使用则滑动续期。
单点登出
终端用户主动结束在 ATKONBASE 的登录会话时,由业务系统发起一次顶层浏览器导航到登出入口:
text
GET https://atkonbase.example.com/api/oauth/logout- 登出后,同一租户下其它接入业务系统再次发起授权时不再免登、须重新认证。
- 登出只结束 ATKONBASE 的登录会话,不影响各业务系统已兑换、仍在有效期内的访问凭证(如需让某个 User Token 立即失效,用桥接篇的按令牌登出)。
- 浏览器无有效会话时重复登出也不会报错(幂等)。
安全约定
⚠️
clientSecret与 User Token 只存业务系统后端,永不下发浏览器。
- 授权码流内置三道防护:强制 PKCE(防授权码被截获后重放)、回调地址精确匹配(防开放重定向)、授权码一次性消费且分钟级失效(防重放)。这些约束由平台强制,接入方照常携带即可。
code_verifier/state是后端机密:由业务后端在发起授权时生成、暂存会话,回调时取回比对——不要放进浏览器可读的地方。- User Token 是代表终端用户的强通道凭证:换取、持有、续期、登出全在后端。前端只触发跳转与接收回调,不经手
clientSecret、code兑换或 User Token。
常见坑
- ⚠️
redirect_uri与登记地址不完全一致:精确匹配会逐字校验协议 / 主机 / 路径 / 查询。规避:发起授权与兑换时用同一个登记过的回调地址常量,不要动态拼查询串。 - ⚠️ 漏带
code_challenge_method=S256:缺省按plain比对,发摘要必失败。规避:发起授权时固定带上该参数(用 SDK 原语则自动携带)。 - ⚠️ 不校验回传
state:跳过 state 比对会留下 CSRF 风险。规避:回调路由先比对state再兑换(用 SDKexchangeCode则内部已校验)。 - ⚠️ 缓存或重试同一个
code:授权码一次性、分钟级失效,复用必失败。规避:拿到即换,失败则重走发起授权。 - ⚠️ 把
clientSecret/code兑换放到前端:等于把令牌签发能力暴露给浏览器。规避:发起跳转可在前端,/oauth/token兑换与clientSecret一律在后端。 - ⚠️ 一个 Client 想复用多个回调地址:一个 Client 只认一个回调落点。规避:每个环境 / 入口登记独立 Client。
下一步
- 拿到 User Token 后怎么带 header 调 V1、解析当前用户 → 上游身份接入与 User Token 桥接模式篇
- 三种身份形态的 header 级语义与失效码 → 认证与身份双通道模式篇
- 授权码流接入原语的各语言用法 → TypeScript SDK / Java SDK
- 兑换失败按
code区分授权码失效 / PKCE 校验失败 / 回调地址不一致 → 认证错误码参考 · Identity Broker 授权码兑换