Widget请求的Rest API身份认证机制实现咨询
嵌入第三方站点的Widget请求防伪造认证方案(NodeJS适配)
这套方案的核心逻辑是把签名生成能力收敛到后端侧,前端永远不会接触到核心密钥,抓包获取的临时凭证有效期极短且和请求上下文强绑定,几乎没有批量复用仿造的价值。
前置配置
- 给每个接入的第三方站点分配唯一的
app_key和app_secret:app_key属于公开信息,可以直接放到Widget的前端初始化参数里;app_secret为核心密钥,仅告知接入方存储在他们自己的后端服务中,绝对不能暴露到前端代码。
完整校验流程
1. 前端Widget签名获取逻辑
Widget初始化时,不要直接请求你的NodeJS服务接口,先请求接入方自己的后端接口,由接入方后端按你指定的规则生成临时签名:
- 签名规则参考:
sign = HMAC-SHA256(app_secret + 10分钟级时间戳 + 接入方站点域名 + 所有请求参数按key排序拼接的字符串) - 接入方后端将
app_key、timestamp、sign三个参数返回给前端Widget
2. 请求携带规则
Widget每次向你的NodeJS后端发起请求时,默认携带上述三个参数,浏览器会自动带上Origin/Referer请求头,不需要额外处理。
3. NodeJS后端校验逻辑
按优先级依次做以下校验,任意一步不通过直接拒绝请求:
- 校验请求携带的
app_key是否在你的合法接入清单中 - 校验
Origin/Referer头的域名是否和该app_key绑定的接入域名一致,无Origin头的请求走降级校验逻辑,单独统计频次 - 校验时间戳有效期:10分钟级时间戳允许前后各1个时间窗口,即签名最多20分钟内有效,避免抓包后长期复用
- 用该
app_key对应的app_secret,按照完全相同的规则重新计算签名,和请求携带的sign做比对,一致才放行
额外加固规则
- 所有请求参数参与签名计算:即使签名被抓包,也只能复用对应请求的固定参数,修改任意请求参数都会导致签名校验失败
- 做
app_key+IP维度的限流,即使签名被破解,也无法发起大规模恶意请求 - 敏感操作接口额外增加人机校验,拦截批量自动化伪造请求
隐私插件拦截
Origin/Referer头的兼容处理:允许这类请求通过签名校验,但单独统计该app_key下无Origin请求的占比,超过阈值触发人工审核,避免被滥用。
示例代码(NodeJS校验逻辑)
const crypto = require('crypto'); // 实际生产环境请存在数据库/配置中心,不要硬编码 const legalAppMap = { 'test_abc123': { appSecret: 'test_secret_456', allowDomains: ['https://customer-a.com', 'https://test.customer-a.com'] } } function validateWidgetRequest(req) { const { app_key, timestamp, sign } = req.headers; // 也可以从query/body取 const origin = req.headers.origin || ''; // 校验app_key合法性 if (!legalAppMap[app_key]) return false; const currentApp = legalAppMap[app_key]; // 校验域名合法性 if (origin && !currentApp.allowDomains.includes(origin)) return false; // 校验时间有效性(10分钟级时间戳,允许前后20分钟误差) const currentTimeSlot = Math.floor(Date.now() / 1000 / 60 / 10); const reqTimeSlot = Math.floor(Number(timestamp) / 60 / 10); if (Math.abs(currentTimeSlot - reqTimeSlot) > 1) return false; // 所有参数按key排序拼接(示例只取query参数,body参数同理处理) const sortedQueryStr = Object.keys(req.query).sort().map(k => `${k}=${req.query[k]}`).join('&'); // 计算签名对比 const calculatedSign = crypto.createHmac('sha256', currentApp.appSecret) .update(`${currentApp.appSecret}${timestamp}${origin}${sortedQueryStr}`) .digest('hex'); return calculatedSign === sign; }
内容的提问来源于stack exchange,提问作者Cycl0n3
相关产品推荐
相关产品推荐

