安全清单与常见错误
认证代码“可以运行”不等于“足够安全”。下面按工作中常见问题复盘。
1. 把 JWT 当成加密数据
错误认识:Payload 有一长串乱码,用户看不到。
事实:Header 和 Payload 通常只是 Base64URL 编码,任何持有者都能解码。
改进:只放最少、非敏感的身份声明,不放密码、密钥和隐私资料。
2. 使用 jwt.decode() 做认证
错误:
const payload = jwt.decode(token);
if (payload.role === "admin") {
// ...
}
改进:服务端认证必须使用 jwt.verify(),同时检查算法、issuer、audience 和业务 Payload 类型。
3. 不限制算法
改进:签发明确设置 algorithm,验证明确设置 algorithms:
jwt.sign(payload, secret, { algorithm: "HS256" });
jwt.verify(token, secret, {
algorithms: ["HS256"],
});
不要启用仅用于旧系统兼容的无效密钥类型或不安全密钥大小选项。
4. Token 永不过期
expiresIn 没有默认值。若不设置,Token 不会自动拥有业务需要的过期时间。
改进:Access Token 使用较短有效期;Refresh Token 使用服务端会话管理其过期和撤销。
5. expiresIn 字符串没有单位
expiresIn: "120" // 不是 120 秒,而会按 ms 时间语法处理
改进:写 120、"120s" 或 "2m",不要写含义模糊的字符串。
6. Secret 写死在源码
错误:
const secret = "my-secret";
改进:通过环境变量或密钥管理服务注入;启动时检查缺失;使用高熵随机值;制定轮换方案。
不要把 .env、私钥或完整 Token 提交到 Git。
7. 明文保存密码
改进:注册和修改密码时使用 argon2.hash(),登录时使用 argon2.verify()。不要用可逆加密代替密码哈希。
8. 用 SHA-256 直接保存密码
问题:通用哈希太快,无法有效抵抗批量猜测。
改进:使用 Argon2id 等专用密码哈希算法,并配合登录限流。
9. 手动固定盐或单独保存盐
node-argon2 默认安全生成盐,并把盐和参数编码在 PHC 字符串中。
改进:初学和大部分普通密码存储直接使用库默认策略,不要自行固定盐。
10. 重新哈希后比较字符串
错误:每次随机盐不同,字符串自然不同。
await argon2.hash(inputPassword) === storedHash;
改进:
await argon2.verify(storedHash, inputPassword);
11. 明文保存 Refresh Token
数据库泄露后,未过期的 Token 可以直接使用。
改进:生成高熵随机 Refresh Token,数据库只保存 SHA-256 摘要。客户端原文只在签发或轮换响应中出现一次。
12. 刷新后旧 Token 继续有效
改进:执行 Refresh Token Rotation,数据库原子替换旧摘要,并考虑重放检测。
13. 退出只删除客户端 Token
改进:服务端撤销会话,同时客户端清理凭证。注意短期 Access Token 是否需要即时撤销取决于业务风险。
14. 返回“账号不存在”
问题:攻击者可以枚举有效账号。
改进:账号不存在和密码错误统一返回:
{ "message": "账号或密码错误" }
注册接口是否提示用户名重复属于产品取舍,但应配合限流和监控。
15. 没有登录限流
Argon2 故意消耗资源,攻击者可以利用大量请求猜密码或消耗服务器。
改进:
- 按 IP、账号和设备维度限流。
- 使用渐进延迟。
- 监控异常失败量。
- 正确配置反向代理,避免错误信任伪造 IP。
可学习 express-rate-limit,但限流策略比安装库本身更重要。
16. 在日志中记录敏感信息
不要记录:
- 明文密码。
- 完整 Access Token。
- 完整 Refresh Token。
- JWT Secret 或私钥。
- 完整
Authorization请求头。
可以记录会话 ID、用户 ID、失败类型和请求追踪 ID,但要遵守隐私和数据保留策略。
17. 忽略 HTTPS
Bearer Token 的含义是“持有者即可使用”。HTTP 明文传输会让中间人读取密码和 Token。
生产环境必须使用 HTTPS,并正确配置反向代理、Cookie 的 Secure 属性和 HSTS 等策略。
18. 认为 localStorage 或 Cookie 绝对安全
localStorage:成功的 XSS 可以读取。HttpOnlyCookie:脚本不能直接读取,但自动携带可能带来 CSRF 风险。
改进:根据前后端部署方式综合设计 CSP、输出转义、CSRF 防护、CORS、SameSite、Secure 和 HttpOnly。
19. 把角色永久相信为 Token 中的值
用户被降权后,旧 Access Token 中的 role 在过期前仍可能是旧值。
改进:
- Access Token 保持短期。
- 高风险操作查询数据库最新权限。
- 角色变化时撤销会话或增加 Token 版本。
20. 混淆 401 与 403
- Token 缺失、无效或过期:
401。 - 身份有效但角色不足:
403。
21. 生产检查清单
- [ ] 密码只保存 Argon2id 哈希。
- [ ] 密码和完整 Token 不进入日志。
- [ ] JWT Secret/私钥由安全配置注入。
- [ ] Access Token 设置较短
expiresIn。 - [ ]
verify()限制algorithms、issuer、audience。 - [ ] 业务 Payload 经过类型/结构校验。
- [ ] 不用
decode()建立信任。 - [ ] Refresh Token 高熵、只保存摘要、支持撤销。
- [ ] 刷新执行原子轮换并考虑重放。
- [ ] 登录和刷新接口有限流与审计。
- [ ] 浏览器端正确处理 XSS、CSRF 和 Cookie。
- [ ] 生产环境全程 HTTPS。
- [ ] 修改密码、封禁账号、角色变更有会话策略。
- [ ] 数据库有唯一约束、索引和事务保护。
- [ ] 密钥轮换和应急撤销方案经过演练。