主数据平台 1.5.1 的文档更新,重点落在应用接入和后端源码分析上。新版接入说明覆盖 Ticket 模式与 OAuth2 授权码模式,并补充了若依系统的接入示例、OAuth2 客户端支持说明,以及授权码、Token 和 state 参数之间的区别。
对开发团队来说,这类文档升级的价值不只是“多了几页说明”,而是把单点登录从概念变成了可以核对、调试和落地的流程。下面用一套可改造的示例,梳理两种协议的边界与工程实践。
两种登录模式,解决不同问题
Ticket 模式通常适合已有统一认证中心、并且希望通过一次性票据完成登录的系统。业务系统收到 Ticket 后,需要通过后端接口向认证中心校验,确认 Ticket 没有被使用过、没有过期,并取得用户身份信息。
OAuth2 授权码模式则更适合标准化的第三方授权场景。浏览器先被重定向到授权端,用户完成认证后,授权端通过回调地址返回一次性 code。业务后端再使用客户端凭证交换 Access Token,最后调用用户信息接口。
两者都可能出现“浏览器跳转”和“回调”,但安全边界不同:
- Ticket 是认证中心签发给业务系统的一次性登录凭证,通常需要服务端校验。
- OAuth2
code是授权码,不是 Access Token,也不能直接当作用户身份令牌。 - OAuth2
state用来关联发起登录的会话并防止 CSRF,不能拿来替代code或 Token。 - Access Token 应保存在服务端会话、受保护的缓存或密钥管理系统中,不应无理由暴露给浏览器。
OAuth2 授权码流程的可运行示例
下面的 Shell 示例演示完整的 Token 交换过程。示例中的域名、客户端 ID 和授权码都是占位值,接入时替换为平台文档提供的实际配置。命令依赖 curl 和 jq。
#!/usr/bin/env bash
set -euo pipefail
AUTH_BASE="https://sso.example.internal"
CLIENT_ID="replace-with-client-id"
CLIENT_SECRET="replace-with-client-secret"
REDIRECT_URI="https://app.example.internal/sso/callback"
CODE="replace-with-one-time-authorization-code"
TOKEN_JSON="$(curl --fail-with-body -sS -X POST "$AUTH_BASE/oauth/token" \\
-H 'Content-Type: application/x-www-form-urlencoded' \\
--data-urlencode "grant_type=authorization_code" \\
--data-urlencode "client_id=$CLIENT_ID" \\
--data-urlencode "client_secret=$CLIENT_SECRET" \\
--data-urlencode "redirect_uri=$REDIRECT_URI" \\
--data-urlencode "code=$CODE")"
ACCESS_TOKEN="$(jq -r '.access_token // empty' <<< "$TOKEN_JSON")"
if [[ -z "$ACCESS_TOKEN" ]]; then
echo "Token exchange failed: $TOKEN_JSON" >&2
exit 1
fi
curl --fail-with-body -sS "$AUTH_BASE/api/userinfo" \\
-H "Authorization: Bearer $ACCESS_TOKEN" | jq .
实际实现中,回调接口还需要校验以下内容:
state是否与服务端保存的登录会话一致。redirect_uri是否与发起授权时完全一致。code是否已经使用或已经过期。- Token 响应中的
token_type、过期时间和权限范围是否符合预期。 - 用户信息接口返回的唯一标识是否能稳定映射到本地账号。
不要把 client_secret 放到前端 JavaScript、移动端包或公开配置文件中。授权码交换应由后端完成,并通过 HTTPS 传输。
Ticket 模式的服务端校验
Ticket 模式可以抽象为三个请求:业务系统创建登录入口,认证中心回调 Ticket,业务后端向认证中心验证 Ticket。一个最小化的伪代码如下,接口路径和字段名需要按平台实际文档调整:
from flask import Flask, request, redirect, session, jsonify
import requests
app = Flask(__name__)
app.secret_key = "replace-in-production"
SSO_VERIFY_URL = "https://sso.example.internal/api/ticket/verify"
APP_HOME = "https://app.example.internal/"
@app.get("/sso/callback")
def sso_callback():
ticket = request.args.get("ticket")
if not ticket:
return jsonify(error="missing ticket"), 400
response = requests.post(
SSO_VERIFY_URL,
json={"ticket": ticket},
timeout=5,
)
response.raise_for_status()
payload = response.json()
if not payload.get("valid"):
return jsonify(error="invalid or expired ticket"), 401
user_id = payload.get("user_id")
if not user_id:
return jsonify(error="missing user identity"), 502
session["user_id"] = user_id
return redirect(APP_HOME)
这段代码只是接入骨架。生产环境还应加入 Ticket 重放防护、请求日志脱敏、认证中心超时处理、用户自动开户策略,以及本地会话过期策略。尤其要避免在日志中打印完整 Ticket、客户端密钥和 Access Token。
若依系统接入时的几个检查点
若依类后台系统通常已经具备用户、角色、菜单和会话管理,因此接入重点不是重新实现整套认证,而是把外部身份映射到本地用户体系。
可以按下面的顺序验证:
- 明确外部用户唯一标识,例如工号、用户 ID 或统一身份中心的
sub。 - 确认首次登录时是自动创建本地用户,还是只允许预先存在的账号登录。
- 将外部角色映射到若依角色时,定义“覆盖”还是“追加”规则。
- 将认证中心退出与本地会话注销行为分开测试。
- 分别测试登录成功、用户取消授权、授权码过期、Ticket 重复使用、回调地址不匹配和用户不存在等分支。
工程团队还可以把接入配置集中管理,避免散落在控制器代码中:
sso:
enabled: true
mode: oauth2
authorize-url: https://sso.example.internal/oauth/authorize
token-url: https://sso.example.internal/oauth/token
userinfo-url: https://sso.example.internal/api/userinfo
client-id: ${SSO_CLIENT_ID}
client-secret: ${SSO_CLIENT_SECRET}
redirect-uri: https://app.example.internal/sso/callback
scopes:
- openid
- profile
其中 client-secret 应通过环境变量、密钥管理服务或部署平台的 Secret 注入。配置文件可以提交到代码仓库,但敏感值不应直接提交。
用文档和源码分析减少接入成本
新增流程图、时序图和后端源码分析文档,对排查“浏览器已经跳回来了但仍然登录失败”这类问题尤其有帮助。阅读时建议沿着三条线索核对:
- 请求线:浏览器跳转到哪里,回调地址是什么,参数在哪一跳产生。
- 凭证线:Ticket、授权码、Access Token 分别由谁签发、在哪里校验、能否重复使用。
- 账号线:外部用户标识如何转换为本地用户,角色和权限在哪一步加载。
如果团队使用 Claude Code 或其他代码助手辅助源码分析,可以把问题拆成可验证的小任务,例如:
请分析登录回调的调用链,不要修改代码。输出:
1. 回调入口文件和方法
2. state、code、access_token 的产生与校验位置
3. 外部用户 ID 映射到本地用户的字段
4. 可能导致重复登录或 Token 泄露的风险
5. 每个结论对应的文件路径和代码行号
这种提示方式把分析范围、输出格式和安全关注点写清楚,便于人工复核,也适合在团队中形成稳定的工程习惯。自动化工具可以帮助定位调用链,但认证协议的最终判断仍需要开发者结合配置、日志和实际请求验证。
上线前检查清单
- 生产环境全链路使用 HTTPS。
state使用不可预测的随机值,并绑定服务端会话。- 授权码只在后端交换,且验证
redirect_uri。 - Ticket 和授权码都设置一次性使用与过期控制。
- Token、客户端密钥和用户敏感信息不写入普通日志。
- 本地账号映射、角色同步和退出登录行为都有明确规则。
- 对认证中心不可用、超时和返回异常建立降级或错误提示。
- 在测试环境覆盖成功、取消、过期、重放、越权和账号不存在场景。
主数据平台 1.5.1 的接入文档升级,真正值得落地的部分是把协议差异、系统边界和调试路径讲清楚。团队可以先用一个低权限测试应用完成完整流程,再接入若依等业务系统,最后补齐日志审计、角色同步和异常恢复。这样既能快速验证协议,也能把上线风险控制在可观测范围内。