SSO Token Exchange 实战全流程拆解:encryption生成、verify校验与对方JWT解析完整指南
2026-03-17
这两天你做的事情,本质上是在打通一个“跨系统信任链路”,它不是简单的接口对接,而是一个典型的 SSO Token Exchange 体系,核心目标是:在不共享密钥、不暴露敏感信息的前提下,让两个系统建立可信身份。
整个过程其实可以抽象成一句话:
“我用一段带签名的身份声明证明我是我,你来验证这段声明确实是我发的,然后你再给我发一个你体系内的通行证。”
下面我把你这两天做的所有关键点,从协议理解、JWT结构、encryption生成、verify设计、解析对方算法、常见坑位,一层一层完整梳理。
一、整体链路到底在干什么
你现在做的是一个“双向信任但单向签发”的流程:
诺明 → 智评云(发起方)
携带一个 JWT(也就是 encryption)
智评云 → 诺明(回调 verify)
确认这个 JWT 是不是你签的
智评云 → 诺明(返回 token)
发放它系统内的 access_token
这中间最关键的不是 token,而是:
JWT 是如何构造的,以及如何证明它是“你发的”。
所以核心分成三块:
encryption(JWT)生成
对方如何解析 JWT
verify 如何反向校验签名来源
二、JWT结构的关键理解(你踩过的核心点)
标准 JWT 是三段:
header.payload.signature
但你这次对接有一个“非标准点”,也是你一开始解析困难的地方:
真正的业务数据,不在 payload 第一层,而是在 payload.sub 里,而且还是一个 JSON 字符串。
也就是说结构是:
payload = {
sub: "一个字符串,但这个字符串本身是JSON",
iat: 时间戳,
exp: 过期时间
}
而 sub 解出来才是:
{
creditCode,
tenantId,
module,
thirdparty,
expireTime,
nonce
}
这就是为什么你写了解析逻辑:
先 base64 解 payload
再取 payload.sub
再 JSON decode 一次
这一步如果漏掉,就会出现“解析出来但没有业务字段”的问题。
本质一句话总结:
payload 是壳,sub 才是肉。
三、encryption生成的完整逻辑
你现在的生成逻辑已经是标准且正确的,可以总结为五步:
第一步:构造 header
{
"alg": "HS256",
"typ": "JWT"
}
第二步:构造 payload(注意 sub 是字符串)
payload = {
"sub": Json.encode(业务字段),
"iat": 当前时间戳,
"exp": 当前时间 + 有效期
}
注意这里有两个“时间体系”:
JWT层:
iat / exp(JWT本身有效期)
业务层:
expireTime(你传给对方的业务过期时间)
这两个不要混。
第三步:Base64Url编码
headerBase64 = base64UrlEncode(header)
payloadBase64 = base64UrlEncode(payload)
注意必须是 Base64Url,不是普通 Base64,否则会解析失败。
第四步:签名
signature = HMACSHA256(
headerBase64 + "." + payloadBase64,
secret
)
第五步:拼接
encryption = headerBase64 + "." + payloadBase64 + "." + signature
这就是最终你发给对方的 encryption。
四、你这次生成过程中踩的几个关键坑
这部分非常有价值,是实战里最容易出问题的地方。
expireTime 和 exp 混淆
你一开始是用一个时间,但其实:
exp 是 JWT 校验用
expireTime 是业务校验用
如果对方只看 expireTime,你 exp 再对也没用。
sub 必须是字符串,不是对象
这个是对方协议的“坑点设计”。
如果你写成:
"sub": { ... }
那对方 decode 后就不认。
必须是:
"sub": "{"creditCode":"xxx"}"
也就是 JSON 字符串。
Base64Url 和 Base64 混用
如果你用标准 Base64,会出现:
/ =
而 JWT 要求:
_ 去掉 padding
否则对方解析会报:
JWT解析失败 / signature不匹配
signature 一点点差错都会失败
比如:
header顺序不同
JSON编码转义不同
空格不同
中文未 UNESCAPED
都会导致签名完全不一样。
五、verify接口的本质作用
很多人会误解 verify 是“再验证一次 JWT”。
其实不是。
verify 的本质是:
让对方确认:
这个 JWT 是不是你这个服务端签出来的。
也就是说,它验证的是“签发主体”,不是“内容结构”。
流程是:
对方拿到 JWT → 解出 payload → 拿 sub →
调用你的 verify 接口 → 把关键字段传回来 →
你校验 → 返回结果
你这边 verify 做的事情,本质上是:
校验参数完整性
校验 expireTime 是否过期
校验 nonce 是否重复(防重放攻击)
校验 tenantId / creditCode 是否合法
返回 success / fail
这里最关键的是 nonce。
nonce 的意义是:
防止别人拿你一次合法请求无限重放。
你这里也设计了:
nonce TTL(默认60秒)
这一步是整个安全体系的核心之一。
六、对方解析算法你已经完全吃透的关键点
通过你反复测试,其实已经把对方解析逻辑摸清楚了,可以总结为:
split JWT(三段)
base64Url decode payload
JSON decode payload
取 sub
JSON decode sub
校验字段
也就是说,对方不会帮你自动解 sub,他们是“手动二次解”。
这就是为什么你必须严格保证 sub 是 JSON字符串。
七、为什么你会出现“解析失败”的问题
你那几次“生成了但解析不出来”,本质原因就三类:
sub不是字符串
Base64编码不对
signature计算不一致
尤其是第二点和第三点,非常隐蔽。
你后来让“只输出encryption字符串”,其实就是在排除干扰项,这是一个非常正确的调试策略。
八、整个安全模型的本质
你现在这个方案,本质上不是 OAuth2,而是一个轻量版的:
JWT + 双向验证 + 临时凭证签发
可以抽象成三层:
第一层:身份声明(JWT)
第二层:签发验证(verify)
第三层:访问凭证(access_token)
它解决了一个关键问题:
在没有预共享登录态的情况下,如何建立系统间信任。
九、从工程角度的最佳实践建议
结合你现在的实现,我给你几个“企业级会用的优化点”:
JWT生成和解析必须封装成独立服务类
不要散落在 controllernonce 必须落库,并带TTL
否则防重放形同虚设所有异常必须标准化
比如:
A1002 | JWT解析失败
A1203 | 请求重复
你已经在往这个方向走了,这是对的
encryption日志必须记录
但注意不要记录敏感字段明文verify接口必须限流 + IP白名单
否则容易被刷
十、你这套方案的价值总结
你这次不是在写一个接口,而是在做一件更重要的事:
把“跨系统信任”变成一套可复制的标准能力。
它未来可以扩展到:
多系统对接
多租户统一认证
外部平台接入
API网关统一鉴权
甚至可以演进成你们自己的:
企业级 SSO 中台能力
一句话总结你这两天的成果:
你已经把“JWT怎么用”,升级成了“JWT怎么构建信任体系”。
这才是这件事真正的价值所在。
发表评论: