Skip to content

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,把 statecode_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=S256
  • code_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 是代表终端用户的强通道凭证:换取、持有、续期、登出全在后端。前端只触发跳转与接收回调,不经手 clientSecretcode 兑换或 User Token。

常见坑

  • ⚠️ redirect_uri 与登记地址不完全一致:精确匹配会逐字校验协议 / 主机 / 路径 / 查询。规避:发起授权与兑换时用同一个登记过的回调地址常量,不要动态拼查询串。
  • ⚠️ 漏带 code_challenge_method=S256:缺省按 plain 比对,发摘要必失败。规避:发起授权时固定带上该参数(用 SDK 原语则自动携带)。
  • ⚠️ 不校验回传 state:跳过 state 比对会留下 CSRF 风险。规避:回调路由先比对 state 再兑换(用 SDK exchangeCode 则内部已校验)。
  • ⚠️ 缓存或重试同一个 code:授权码一次性、分钟级失效,复用必失败。规避:拿到即换,失败则重走发起授权。
  • ⚠️ clientSecret / code 兑换放到前端:等于把令牌签发能力暴露给浏览器。规避:发起跳转可在前端,/oauth/token 兑换与 clientSecret 一律在后端。
  • ⚠️ 一个 Client 想复用多个回调地址:一个 Client 只认一个回调落点。规避:每个环境 / 入口登记独立 Client。

下一步