HMAC签名校验localStorage会员等级旨在即时发现篡改:写入时用动态密钥签名并存时间戳,读取时重算比对且校验时效;密钥须内存隔离、禁硬编码;后端二次校验权限,前端仅作辅助防护。

直接用 HMAC 签名校验 localStorage 中的会员等级,不是为了“锁住数据”,而是让篡改行为一读即现——哪怕用户手动把 memberLevel 改成 "vip",下一次页面加载时就能立刻识别并拒绝该值。
签名生成:写入前加“防伪钢印”
每次需要保存会员等级时(例如登录成功后),不单独存等级,而是连同签名一起存:
- 构造原始数据字符串,如:
"level=pro|ts=1748042015"(含时间戳防重放) - 使用前端已知但**不硬编码、不存于 storage** 的密钥(如从服务端动态获取的一次性 sessionKey,或页面加载时生成的内存内随机 salt)
- 调用 HMAC-SHA256 计算签名:
hmac("level=pro|ts=1748042015", key),得到 64 位十六进制字符串 - 拼接并存入:
localStorage.setItem("user-membership", "level=pro|ts=1748042015|sig=a1b2c3...")
签名验证:读取时验“钢印是否完整”
页面初始化或关键操作前(如进入付费功能页),必须验证该字段:
- 读取完整字符串,用
|拆分为[dataPart, sigPart] - 提取
dataPart(如"level=pro|ts=1748042015"),用**相同密钥和算法**重新计算 HMAC - 严格比对新签名与
sigPart:不相等 → 数据被篡改 → 视为无效等级,清空该项并回退到默认(如"basic") - 额外检查时间戳:若
ts超过 24 小时,也视为过期,避免长期缓存导致权限滞留
密钥管理:安全的命脉不在存储,而在隔离
HMAC 的安全性完全依赖密钥不泄露。绝不能:
- 把密钥写死在 JS 源码里(会被直接看到)
- 存在 localStorage 或 cookie 中(攻击者可读)
- 用固定全局 salt(易被逆向)
推荐做法:
- 登录成功后,服务端返回一个短期有效的、绑定当前会话的
signingKey(JWT 或加密响应体中携带),仅存于内存变量 - 或在页面加载时,由后端通过
<script>注入一段带随机密钥的初始化代码(非纯文本,可简单混淆),确保密钥不出现在网络请求或 devtools 全局作用域
配套防御:签名只是最后一道哨兵
单靠签名无法阻止篡改发生,它只负责“事后发现”。需叠加基础防护:
- 会员等级不作为唯一权限判断依据——所有敏感操作(如开通权益、访问视频)必须由前端发起带签名的请求,后端再次校验并以服务端状态为准
- 避免使用静态键名
"memberLevel",改用运行时拼接(如"mem" + "ber" + "_lvl_" + Date.now().toString(36).slice(-2)),干扰自动化篡改脚本 - 监听
storage事件:若检测到本域其他标签页修改了该 key,立即触发本地验证,防止跨页污染

















