跟三方做OA租户凭证交换
2026-03-12
诺明获取智评云token步骤 诺明系统 -> 智评云 | | jwt(诺明租户ID+统一社会信用代码+expireTime过期时间+nonce) v 智评云解密校验 | | 调用诺明 verify接口,验证签名是否出自服务端颁发 v 诺明验证 | v 智评云颁发token -------------------- 智评云获取诺明token步骤 智评云 ->诺明系统 | | jwt(智评云租户ID+统一社会信用代码+expireTime过期时间+nonce) v 诺明系统解密校验 | | 调用智评云 verify接口,验证签名是否出自服务端颁发 v 智评云验证 | v 诺明颁发token
我问:
https://norming.yuque.com/gdgncy/tech/cfz3hxahbhuiln2g?singleDoc=
读取下这个网址里面:智评云租户凭证交换
| jwt(诺明租户ID+统一社会信用代码+expireTime过期时间+nonce)
其中nonce给我浅白解释下,以及: 调用诺明 verify接口,验证签名是否出自服务端颁发
这个验证如何去做?整体都给我解析一下,我第一次做这种对接。不太明白。
AI答:
好,这次信息就完整多了。你这套方案本质上不是标准意义上的“账号密码换 token”,也不是传统 OAuth2 里那种“提前配好 client_id/client_secret 再换 token”,而是:
双方先用一个短时效、带签名的“临时身份证明”来证明自己是谁;再通过对方服务端二次核验这个证明确实是服务端发出来的;最后由被请求方颁发正式业务 token。
这个思路是对的,而且很适合你说的那种“一体化集成、尽量减少人工配置”的场景。
我下面把它彻底讲透,并顺手帮你把原方案补强一下。
一、先把整个方案翻译成人话
你们现在想解决的问题,其实是这个:
诺明和智评云两个系统,要互相调用接口。
但如果按传统做法,就得:
双方提前人工配置一堆 client_id、client_secret
双方都要长期保存对方的密钥
客户在打通时要填很多参数
一旦密钥泄漏,风险持续很久
你们现在换了一种思路:
不长期保存对方 secret,而是每次临时生成一个短期有效的 JWT 签名票据。
然后被请求方收到后,不是直接信,而是:
先本地解出里面的内容
校验企业身份、过期时间这些基础信息
再反向调用签发方的 verify 接口
让签发方确认:这个签名确实是我自己服务端签发的,不是伪造的
确认无误后,再颁发正式 token
你可以把它想成:
先拿“临时介绍信”去办事;对方不只看介绍信内容,还要打电话回原单位查证这封介绍信是不是他们真的开过。
这就比“只看介绍信内容”更安全。
二、这个方案里的几个核心角色分别是谁
为了不绕,我先把角色固定一下。
1、发起方
就是当前想向对方申请 token 的系统。
比如:
诺明想拿智评云 token,那么诺明就是发起方。
2、签名签发方
也是发起方自己。
因为 JWT 是发起方自己服务端签的。
比如:
诺明生成 signature,那么签发方就是诺明。
3、验证方
就是签名签发方自己的 verify 接口。
比如:
智评云收到诺明的 signature 后,会去调诺明的 verify 接口验证。
所以验证这个签名真伪的,仍然是诺明自己。
4、token 颁发方
是谁收到请求,谁颁发 token。
比如:
诺明向智评云申请 token,那么 token 最终由智评云颁发。
三、整个交互流程到底在干什么
你原文里已经写了流程,但第一次看的人容易迷糊,因为“解密校验”“verify 验签”“颁发 token”这几个动作容易混在一起。
我给你拆成三个层次。
第一层,本地读取并做基础校验
被请求方先把 JWT 拿到手,解析出里面的字段。
比如解析出:
租户 ID
统一社会信用代码
expireTime
nonce
然后先做最基础的判断:
字段是不是齐全
统一社会信用代码是否能映射到本系统中的目标租户
expireTime 是否过期
nonce 是否合法
这一步可以理解为:
先检查这张介绍信表面上有没有明显问题。
注意,这一步不是最终安全校验,只是初筛。
第二层,调用对方 verify 接口做“签发真实性确认”
这是你这套方案的关键。
因为即使 JWT 结构合法,字段看起来也没问题,仍然不能说明:
这个 JWT 就一定是对方服务端签发的。
所以,被请求方还要调用发起方的 verify 接口,带上:
thirdparty:告诉对方“我是哪个第三方系统在验证”
signature:把刚收到的 JWT 原样传回去
然后由发起方自己的服务端来确认:
这个 signature 是不是我签的
签名是否有效
有没有过期
nonce 是否已被使用过
是否允许给这个 thirdparty 使用
这一步相当于:
打电话回开介绍信的单位,问一句:这信是你们真开的吧?
第三层,通过后再颁发正式 token
如果 verify 成功,被请求方才认为这次身份可信,于是颁发自己的业务 token。
这个 token 才是后续真正用来调业务接口的凭证。
也就是说:
JWT 不是最终业务 token,而是一个“拿 token 的临时票据”。
这点一定要分清。
四、nonce 到底是什么,用最白的话再讲一遍
你最容易卡住的点还是 nonce,我再用最实在的话讲一次。
nonce 就是:
一次性随机编号。
它的作用是防止别人把之前抓到的一次合法请求,再原封不动重放一次。
比如:
诺明今天发给智评云一个 signature。
中间如果有人把这个请求包截下来,理论上他以后还能拿着这个旧包再发一遍。
如果没有 nonce,或者 nonce 不校验重复,那系统可能会把它当成新的合法请求。
所以要加 nonce,而且 verify 时要检查:
这个 nonce 是否已经被用过。
用过就拒绝。
你可以把它理解成饭店取号:
今天你拿的是 A102。
这个号只能叫一次。
叫完就废。
你不能明天又拿着 A102 过来继续排队。
这就是 nonce。
五、你文档里“解密校验”这几个字,其实更准确的说法应该改一下
你原文里写:
“智评云解密校验”
“诺明系统解密校验”
这里容易让开发误解成“JWT 是加密的”。
但严格说,大多数 JWT 只是:
编码 + 签名,不一定加密。
所以更准确的表述应该是:
解析并校验 JWT 内容
或者:
解码 JWT 并进行基础字段校验
因为 JWT 的 payload 通常是可以被解出来看的,只是不能随便改;改了签名就不对。
所以你这里建议文档措辞改成:
“解析 signature,提取租户ID、统一社会信用代码、expireTime、nonce,并进行基础合法性校验”
比“解密校验”更准确。
六、你这套方案真正的安全核心是什么
你这个方案安全性,不是来自“统一社会信用代码保密”。
因为统一社会信用代码本来就是公开可查的。
真正的安全性来自四层组合:
1、短时效
expireTime 很短,比如 3 分钟或 5 分钟。
就算被截获,能用的时间窗口也很短。
2、签名
只有服务端掌握签名密钥。
别人没法伪造。
3、二次 verify
被请求方不直接盲信 signature,而是反向调签发方 verify。
这样能最大限度确认签名确实来自服务端。
4、nonce 防重放
就算旧请求在有效期内,也不能重复用。
所以它不是靠“字段秘密”保证安全,而是靠:
服务端签名 + 短时效 + 一次性随机数 + 服务端二次确认。
七、你这套流程里,每一步应该怎么理解
下面我按“诺明向智评云申请 token”为例,给你彻底展开。
1、诺明生成 signature
诺明服务端根据以下信息生成 JWT:
诺明租户 ID
统一社会信用代码
expireTime
nonce
这里建议最好再加两个字段:
iss:签发方标识,比如 norming
thirdparty:目标第三方,比如 zhipingyun
也就是说,不要只写业务字段,最好把“我是给谁签的”也写进去。
否则以后一个 token 被拿去别的第三方场景复用,就容易出问题。
建议 payload 类似这样:
{
"iss": "norming",
"thirdparty": "zhipingyun",
"tenantId": "A1",
"creditCode": "913xxxxxxxxxxxxxxx",
"expireTime": 1770000000,
"nonce": "8f3c5a7d2b9e41..."
}2、诺明调用智评云 getTokenBySso
请求体里传:
{
"signature": "eyJhbGciOi..."
}智评云收到后,不是立刻发 token。
3、智评云先做基础校验
这一步建议校验这些内容:
第一,signature 格式是否合法
是不是空,JWT 格式对不对。
第二,payload 字段是否完整
tenantId、creditCode、expireTime、nonce 是否都存在。
第三,统一社会信用代码是否能定位对应租户关系
也就是:
这家企业在智评云是否存在。
它和诺明租户关系是否正确。
第四,时间是否过期
当前时间 > expireTime 就拒绝。
第五,目标第三方是否匹配
如果 payload 里写的是给 zhipingyun 用的,那当前接口必须确实是智评云自己的。
4、智评云调用诺明 verify
请求大概如下:
{
"thirdparty": "zhipingyun",
"signature": "eyJhbGciOi..."
}这个 thirdparty 很重要,它代表:
我是智评云,我正在验证这个签名能不能用于我这个系统。
5、诺明 verify 接口做什么
诺明 verify 不只是“解开看一下”这么简单,而应该做完整判断:
第一,校验签名是否正确
确认确实是诺明服务端签出来的。
第二,校验 iss 是否是 norming
防止拿别人的 token 冒充。
第三,校验 thirdparty 是否匹配请求方 zhipingyun
防止本该给 A 系统的票据,被 B 系统拿去用。
第四,校验 expireTime 未过期
过期直接拒绝。
第五,校验 nonce 未被使用过
如果已使用,拒绝。
第六,校验 tenantId、creditCode 是否确实是合法租户
比如 tenantId 存不存在,是否与企业信息一致。
第七,通过后把 nonce 标记为已使用
这一点很关键。否则就无法防重放。
然后返回:
{
"success": true
}或者失败时返回错误类型和原因。
6、智评云拿到 verify 成功结果后再颁发 token
这时智评云才能放心地给诺明发自己的 access_token。
也就是说,verify 成功,不代表业务 token 已经返回;它只是“签名真实性确认通过”。
之后才进入真正 token 颁发逻辑。
八、反过来智评云向诺明申请 token,逻辑完全一样
只是角色互换。
原理没有任何变化:
智评云签 JWT
智评云调用诺明 getTokenBySso
诺明做基础校验
诺明回调智评云 verify
verify 通过
诺明颁发 token
本质上就是对称双向互信。
九、你原方案里有几个地方,我建议进一步补强
你现在这个方案已经能用了,但要做得更稳,我建议补下面这些点。
1、signature 这个名字可以保留,但概念上要清楚它不是普通签名串,而是 JWT 票据
因为很多开发一看到 signature,会以为只是一个单纯的哈希签名值。
实际上你这里传的是完整 JWT。
所以文档里最好写成:
signature:由服务端签发的 JWT 票据,内含 tenantId、creditCode、expireTime、nonce 等信息
这样更不容易误解。
2、建议 payload 增加 iss、thirdparty、iat、jti
你现在只有四个字段,有点薄。
建议至少加:
iss
签发方标识
比如 norming 或 zhipingyun
thirdparty
目标第三方系统
比如 zhipingyun 或 norming
iat
签发时间
jti
票据唯一 ID
nonce 和 jti 有点像,但用途可区分:
nonce 偏向防重放随机串
jti 偏向票据唯一标识
如果你不想太复杂,也可以保留 nonce 作为唯一票据号,不一定非得单独加 jti。
3、verify 接口不要只返回 success
建议至少返回更明确的信息,便于排查。
比如:
{
"success": true,
"data": {
"tenantId": "A1",
"creditCode": "913xxxxxxxxxxxxxxx",
"thirdparty": "zhipingyun"
}
}失败时:
{
"success": false,
"error": [
{
"type": "TOKEN_EXPIRED",
"msg": "signature已过期"
}
]
}这样方便日志记录和问题定位。
4、nonce 校验必须落地,不要只写在概念上
很多方案文档里写了 nonce,但代码没真正防重放。
正确做法一般是 Redis:
key 类似:
sso:nonce:norming:8f3c5a7d2b9e41
设置过期时间与 signature 有效期一致,比如 300 秒。
verify 时逻辑建议是:
查询 nonce 是否存在
存在则拒绝,说明重放
不存在则验签通过后写入 Redis
5、统一社会信用代码不是唯一安全依据,只是关联租户关系的索引
这个要讲清楚。
因为统一社会信用代码是公开信息,所以它不是“秘密”,只是“定位企业”的条件。
真正的安全校验还得靠签名和 verify。
所以文档里可以补一句:
“统一社会信用代码仅用于建立双方租户映射关系,不作为单独安全凭证使用。”
这句话很关键。
6、自建客户场景,需要把 baseurl 解析逻辑讲清楚
你文档最后提到:
“双方自建需要在请求获取token前,解析出统一社会信用代码对应的获取baseurl”
这个思路是对的,但还需要明确:
问题本质
对于 SaaS 主站,接口地址通常固定。
但自建客户的部署地址可能各不相同。
所以你不能只知道企业是谁,还得知道:
这家企业对应的 getTokenBySso 和 verify 接口地址在哪台服务器上。
所以要有一张映射关系表
建议至少维护:
统一社会信用代码
租户 ID
sso_baseurl
是否启用
环境类型
更新时间
比如:
| credit_code | tenant_id | sso_baseurl | status |
|---|---|---|---|
| 913xxx | A1 | https://a1.zhipingyun.com | 1 |
然后请求前根据统一社会信用代码先查 baseurl。
这一点要写进实现说明里,不然开发会不知道从哪找地址。
十、给你一个更清楚的“协议理解版”
我帮你把原文整理成更适合开发和产品都能看懂的话。
方案目标
实现诺明与智评云之间的自动化 token 交换,基于企业统一社会信用代码建立租户映射关系,在尽量减少人工配置项的前提下,完成双方系统一体化集成。
适用场景
适用于 SaaS 平台之间或 SaaS 与自建部署之间的双向 SSO Token Exchange 场景,用于在不长期保存对方 client_secret 的情况下,实现受控、安全的 token 获取。
核心思路
双方不长期持有彼此的 client_secret,而是由请求方服务端生成一个短时效 JWT 票据作为临时身份证明。
被请求方收到该票据后,先解析并进行基础业务校验,再调用请求方提供的 verify 接口确认该票据确由其服务端签发且未被重复使用。
验证通过后,由被请求方颁发自身业务 token。
票据包含字段
建议至少包含以下字段:
tenantId:请求方租户 ID
creditCode:企业统一社会信用代码
expireTime:票据过期时间
nonce:一次性随机数,用于防重放
iss:签发方系统标识
thirdparty:目标第三方系统标识
iat:签发时间
十一、我帮你把两个流程重写成更清晰的版本
1、诺明获取智评云 token 流程
第一步
诺明服务端生成 signature,内容包括:
诺明租户ID、统一社会信用代码、expireTime、nonce 等字段,并使用诺明服务端密钥签名生成 JWT。
第二步
诺明调用智评云接口:
POST xxx/getTokenBySso
请求体:
{
"signature": "jwt字符串"
}第三步
智评云收到请求后,解析 JWT,完成以下基础校验:
signature 格式是否合法
字段是否完整
统一社会信用代码是否可映射到智评云侧租户
expireTime 是否过期
目标 thirdparty 是否为智评云
第四步
若基础校验通过,智评云服务端调用诺明 verify 接口:
POST xxx/verify
请求体:
{
"thirdparty": "zhipingyun",
"signature": "诺明signature"
}第五步
诺明 verify 接口对 signature 进行服务端校验,包括:
签名是否有效
是否由诺明服务端签发
expireTime 是否过期
nonce 是否已被使用
tenantId 与统一社会信用代码是否匹配
thirdparty 是否与 zhipingyun 一致
验证通过后返回:
{
"success": true
}否则返回失败原因。
第六步
智评云收到 verify 成功响应后,向诺明颁发智评云业务 token。
2、智评云获取诺明 token 流程
同理,只是双方角色互换,不再重复。
十二、你这个方案与 OAuth2 相比,优势和边界是什么
优势
第一,减少人工配置
不用让客户手工保存和维护长期 secret。
第二,降低长期泄漏风险
没有长期静态 secret,更多使用短时效票据。
第三,适合双向集成
诺明和智评云都可以用同样模式互相换 token。
第四,适合企业一体化落地
统一社会信用代码天然适合做企业映射索引。
边界
第一,它不是标准 OAuth2
所以协议细节、字段定义、错误码、重放规则,都要你们自己约定清楚。
第二,verify 接口是强依赖
如果 verify 服务挂了,token 交换就会受影响。
第三,对时钟要求较高
双方服务器时间不能差太大,否则 expireTime 容易误判。
所以建议再补一个“允许时钟偏差 60 秒”的约定。
十三、给你一个最关键的实现建议:verify 不要查“签名是否存在”,而要做“可验证且可消费”
你原文写:
“缓存验证签名是否存在,验证通过后返回”
这句话有点模糊,容易让开发理解成:
“只要缓存里有这个 signature,就算通过。”
这个不够严谨。
更合理的逻辑应该是:
verify 接口不是简单查是否存在,而是对票据做一次完整消费式校验。
也就是说:
验签
校验过期时间
校验 thirdparty
校验 tenantId 和 creditCode
校验 nonce 未消费
将 nonce 标记为已消费
返回成功
这样才是真正的“防伪 + 防重放”。
十四、给你一个你最容易记住的总口诀
你就记这一句:
统一社会信用代码负责“找企业”,JWT 签名负责“证身份”,expireTime 负责“限时效”,nonce 负责“防重放”,verify 接口负责“让签发方亲口确认这票是真的”。
这句话记住,你整个对接思路就清了。
十五、如果让我帮你继续往下落地,我建议下一步做这三件事
第一,整理成一版正式接口协议说明,把字段、校验规则、错误码全部补齐。
第二,给你输出一版 PHP/Yii2 的 getTokenBySso 和 verify 示例代码。
第三,顺手把 Redis 的 nonce 防重放方案也一起写进去。
发表评论: