自定义认证令牌存储最佳实践及安全风险规避方案咨询
针对Widget令牌盗用风险的解决方案
1. 绑定请求上下文的令牌验证
- 令牌嵌入请求来源特征:生成令牌时,把当前客户站点的域名、浏览器UA的哈希片段(比如取UA前50位+域名做SHA256哈希)作为加密参数和用户ID一起加密。后端验证时,先解密令牌,重新计算当前请求的来源特征哈希,和令牌内的比对,不一致直接拒绝。
- PHP代码示例:
// 生成令牌时的来源特征计算 $sourceHash = hash('sha256', $_SERVER['HTTP_HOST'] . substr($_SERVER['HTTP_USER_AGENT'], 0, 50)); // 将$sourceHash与用户ID一起加密生成令牌
- PHP代码示例:
- 短令牌+刷新机制:拆分令牌为访问令牌(15-30分钟有效期,存在Widget内存中,页面刷新即失效)和刷新令牌(7天左右有效期,存在客户站点的HttpOnly、Secure、SameSite=Strict Cookie中)。每次接口请求用访问令牌,过期后通过Cookie自动携带的刷新令牌向后端换取新的访问令牌,同时更新刷新令牌。
2. 浏览器指纹辅助验证
- 绑定稳定指纹:用户登录时,收集浏览器的稳定特征(屏幕分辨率、时区、语言列表、WebGL指纹哈希等),将指纹哈希和用户ID一同加密到令牌内。后续每次请求Widget接口时,前端重新计算当前浏览器的指纹哈希,传给后端和令牌内的哈希比对,匹配度低于阈值(比如90%)则触发二次验证(短信/邮箱验证码)。
- JS代码示例(指纹计算):
async function generateFingerprint() { const features = [ navigator.userAgent, screen.width, screen.height, Intl.DateTimeFormat().resolvedOptions().timeZone, navigator.language ].join('|'); const encoder = new TextEncoder(); const hashBuffer = await crypto.subtle.digest('SHA-256', encoder.encode(features)); // 转成十六进制字符串 return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join(''); }
- JS代码示例(指纹计算):
- 设置匹配阈值:允许指纹有小范围变化(比如用户调整窗口大小),通过计算特征相似度判断是否合法,而非要求完全一致。
3. 限制令牌使用范围
- 绑定Widget实例:令牌内嵌入用户在该客户站点创建的Widget ID,后端验证时仅允许对应Widget的请求使用该令牌,避免令牌跨Widget盗用。
- 单会话令牌机制:后端存储每个用户的有效令牌列表,每次令牌使用时检查是否为当前活跃会话的令牌。若检测到同一令牌在不同上下文(不同IP、不同指纹)被使用,立即失效该令牌并通知用户。
4. 强化存储安全
- 优先使用HttpOnly Cookie:刷新令牌存在客户站点的Cookie中,标记
HttpOnly(JS无法读取)、Secure(仅HTTPS传输)、SameSite=Strict(仅同站请求携带),从根源减少XSS盗用风险。 - 前端二次加密:若必须用前端存储(如Cookie无法使用),用客户站点域名的哈希作为密钥,对令牌做AES加密后再存入localStorage。即使令牌被窃取,没有域名密钥也无法解密使用。
内容的提问来源于stack exchange,提问作者b4tch
相关产品推荐
相关产品推荐

