认证安全
规则
- 使用 bcrypt(成本因子 12 以上)或 argon2id 对密码进行哈希处理 — 绝不存储明文或使用 SHA/MD5
- 对登录端点进行速率限制:每个 IP 每分钟最多 5 次尝试,每个账户每小时最多 10 次
- 实施指数级锁定:失败尝试后 1 秒、2 秒、4 秒、8 秒... 在 10 次失败后硬锁定 15 分钟
- 密码重置令牌:加密安全的随机令牌,一次性使用,最迟在 1 小时内过期
- 返回通用错误:“邮箱或密码无效” — 绝不透露哪个字段有误
- 强制执行密码最小长度为 8 个字符,检查是否出现在已泄露密码列表中(HaveIBeenPwned API)
import bcrypt from "bcrypt";
const hash = await bcrypt.hash(password, 12);
const valid = await bcrypt.compare(password, storedHash);
if (!valid) {
await incrementFailedAttempts(userId);
return res.status(401).json({ error: "Invalid email or password" });
}
await resetFailedAttempts(userId);
async function checkLockout(userId: string) {
const attempts = await getFailedAttempts(userId);
if (attempts.count >= 10) {
const lockoutEnd = attempts.lastAttempt + 15 * 60 * 1000;
if (Date.now() < lockoutEnd) throw new Error("Account temporarily locked");
}
}
避免
- 以明文、base64 或可逆加密方式存储密码
- 使用 SHA-256 处理密码 — 它速度很快,意味着容易遭受暴力破解
- 在登录或密码重置时泄露邮箱是否存在
- 密码重置链接永不过期或可重复使用
- 允许来自单个 IP 的无限登录尝试