安全清单与常见错误

认证代码“可以运行”不等于“足够安全”。下面按工作中常见问题复盘。

1. 把 JWT 当成加密数据

错误认识:Payload 有一长串乱码,用户看不到。

事实:Header 和 Payload 通常只是 Base64URL 编码,任何持有者都能解码。

改进:只放最少、非敏感的身份声明,不放密码、密钥和隐私资料。

2. 使用 jwt.decode() 做认证

错误:

const payload = jwt.decode(token);
if (payload.role === "admin") {
  // ...
}

改进:服务端认证必须使用 jwt.verify(),同时检查算法、issueraudience 和业务 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 故意消耗资源,攻击者可以利用大量请求猜密码或消耗服务器。

改进:

可学习 express-rate-limit,但限流策略比安装库本身更重要。

16. 在日志中记录敏感信息

不要记录:

可以记录会话 ID、用户 ID、失败类型和请求追踪 ID,但要遵守隐私和数据保留策略。

17. 忽略 HTTPS

Bearer Token 的含义是“持有者即可使用”。HTTP 明文传输会让中间人读取密码和 Token。

生产环境必须使用 HTTPS,并正确配置反向代理、Cookie 的 Secure 属性和 HSTS 等策略。

18. 认为 localStorage 或 Cookie 绝对安全

改进:根据前后端部署方式综合设计 CSP、输出转义、CSRF 防护、CORS、SameSiteSecureHttpOnly

19. 把角色永久相信为 Token 中的值

用户被降权后,旧 Access Token 中的 role 在过期前仍可能是旧值。

改进:

20. 混淆 401403

21. 生产检查清单