Node.js安全登錄策略:最佳實踐與核心技巧
本文介绍了一些针对 Node.js 的安全登录策略,通过采用多种安全措施来保护用户的登录凭证、会话安全和账户访问等方式。
一些关键的安全功能1. 加盐的密码哈希
密码使用强哈希算法(例如,bcrypt)以及每个密码的随机盐进行哈希。这可以确保即使哈希后的密码被泄露,也无法轻易还原成原始密码。
为什么它这么重要:
- 防止以明文形式保存密码。
- 加盐处理让攻击者难以通过预计算散列表(如彩虹表)来猜测密码。
实施方案:
import bcrypt from 'bcrypt';
const 加密密码 = async (password: string): Promise<string> => {
const saltRounds = 10;
const salt = await bcrypt.genSalt(saltRounds);
return await bcrypt.hash(password, salt);
};
// 创建用户并保存到数据库
const newUser = new User({ username, password: 加密密码 });
await newUser.save();
// 登录 API
const { username, password } = req.body;
const user = users.find((u) => u.username === username);
if (!user) return res.status(400).send('找不到该用户');
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) return res.status(400).send('密码不正确');
全屏查看 全屏退出
2. 基于令牌的认证。
JWT(JSON Web Token),用于令牌验证,实现客户端与服务器之间的无状态认证。用户登录后会获得一个JWT,然后在每个后续请求的Authorization头中发送此JWT。
为什么它很重要呢:
- 无状态:无需在服务器上保存会话状态。
- 令牌可以携带声明(claims,如用户 ID、roles)而不暴露敏感信息。
实施:
import jwt from 'jsonwebtoken';
const generateToken = (userId: string) => {
// 生成一个基于用户ID的JWT令牌,有效期为1小时
return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '1h' });
};
切换到全屏模式 | 退出全屏
3. 刷新 token
刷新代币用于无需用户重新登录的情况下获取新的访问代币。这确保了用户可以享受顺畅的体验,同时也保障了安全性。
这里有一个使用JWT认证并通过HTTP-only Cookie存储刷新令牌的GitHub仓库
为什么这很重要?
- 通过使用短期访问令牌(access token)来减少暴露。
- 刷新令牌(refresh token)更安全地存储(例如存储在数据库或 HTTP-only cookie 中)。
实施:
请注意,根据上下文可能需要在“实施”后面添加一个冒号(:),以保持与英文原文结构一致。如果上下文确定了这一点,最终翻译应为:
实施:
// 生成刷新令牌的函数
const generateRefreshToken = (userId: string) => {
return jwt.sign({ userId }, process.env.REFRESH_SECRET, { expiresIn: '7d' });
};
切换到全屏 退出全屏
4. HTTP-only(仅HTTP)Cookies
将敏感令牌(如刷新令牌)存储在 HTTP-only 的 cookie 中。这可以防止 JavaScript 访问令牌,从而保护它们不受 XSS(跨站脚本)攻击的影响。
为什么它很重要?
- 保护令牌免受恶意脚本窃取。
- 降低令牌因客户端代码泄露的风险。
实现:
响应对象设置cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: "安全: 生产环境中为true",
sameSite: "同一站点策略: '严格'",
});
全屏 退出全屏
5. JWT 黑名单
JWT 令牌本身并不具备撤销功能。为了使令牌失效(比如用户退出时),会使用 JWT 黑名单。通常会使用数据库或 Redis 来存储这些已失效的令牌,直到它们自然过期。
为什么它很重要。
- 防止已发放的令牌在注销或账户被篡改后仍被使用。
实施阶段:
const 黑名单令牌处理 = async (token: string) => {
await redisClient.set(token, '列入黑名单', 'EX', tokenExpirationTime);
};
点击全屏进入,点击退出全屏
6. 一次会话。
确保任何时候一个用户只有一个会话处于活动状态。当创建一个新的会话时,失效之前的 tokens 或会话。
为什么它很重要?
- 防止从多个设备或地点同时登录,提高账户安全性。
实施:
// 用户表的Schema:
sessionId: { type: String, unique: true }
// 登录API
const newSessionId = generateAccessToken(user);
user.sessionId = newSessionId;
await user.save();
// 认证API
const decodedToken: any = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET!); // 请使用您的JWT密钥替换
const sessionIdFromToken = decodedToken.sessionId;
if (user.sessionId !== sessionIdFromToken) {
return res.status(403).json({ message: '会话已失效。您已在其他设备上登录。' });
}
全屏 退出全屏
7. 理想的会话超时时间
会话会在一段时间的不活动后自动过期。实施一个会话过期计时器,在用户长时间不活跃后自动登出用户。
为什么它很重要?
- 减少因用户忘记注销而导致未授权用户访问登录会话的风险。
实现:
const sessionExpirationTime = 15 * 60 * 1000; // 15分钟(900000毫秒)
const IDLE_TIMEOUT = 15 * 60 * 1000;
// 用户模式定义
lastActivity: { type: Date, default: Date.now }, // 记录最后活动时间戳
// 在认证中间件里
const now = new Date();
const idleTime = now - new Date(user.lastActivity);
if (idleTime > IDLE_TIMEOUT) {
// 会话因长时间无活动而过期,尽管令牌仍然有效
return res.status(401).json({ message: '会话因长时间无活动而过期' });
}
// 否则,更新最后活动时间戳
user.lastActivity = now;
await user.save();
进入全屏 退出全屏
8. 账号锁定机制。
通过在几次登录失败尝试后锁定账户来防止暴力破解攻击。实行基于时间的锁定,在多次登录失败后暂时锁定账户。
为什么它很重要?
- 通过限制登录次数来防止暴力破解尝试。
实施:
// Schema
failedLoginAttempts: { type: Number, default: 0 },
lockoutUntil: { type: Date, default: null }, // 跟踪锁定时间
const MAX_ATTEMPTS = 3;
const LOCK_TIME = 15 * 60 * 1000; // 15分钟
// MongoDB和Mongoose示例
const handleFailedLogin = async (email: string) => {
const user = await User.findOne({ email });
if (!user) return false;
if (user.lockoutUntil 且 user.lockoutUntil 在当前时间之后) {
return true; // 账户仍然被锁定
}
user.failedLoginAttempts += 1;
if (user.failedLoginAttempts >= MAX_ATTEMPTS) {
user.lockoutUntil = new Date(Date.now() + LOCK_TIME); // 锁定30分钟
}
await user.save(); // 保存用户数据
return user.lockoutUntil 且 user.lockoutUntil 在当前时间之后; // 返回账户是否被锁定
};
// 如果登录成功则重置登录尝试次数
全屏, 退出全屏
9. 安全的密码重置程序
用户可以通过一个安全的过程来重置密码。使用一次性有效令牌(OTP)通过电子邮件发送给用户,来重置密码。确保该令牌在短时间内失效。
为什么它很重要。
- 为用户提供找回账户的同时确保账户安全。
实施:
// 用户模式定义
resetPasswordToken: String,
resetPasswordExpires: Date,
/* 使用 crypto.randomBytes() 生成一个安全的十六进制值,而不是使用 Math.random(),因为 Math.random() 生成的十六进制值更容易被攻击者猜测或暴力破解。这样做更安全,不容易被破解。*/
const generateResetToken = () => {
return crypto.randomBytes(20).toString('hex');
};
const token = generateResetToken();
user.resetPasswordExpires = Date.now() + 3600000; // 一小时后
进入全屏 退出全屏
10. 密码过期及限制最近三个密码
这就是怎么回事。
- 密码历史记录:
- 系统会保存用户密码历史记录中的最后三个哈希化密码。
- 当用户尝试更改密码时,系统会将新密码与之前的密码进行比较,以防止密码重复。
- 密码过期:
-
系统会跟踪用户使用当前密码的时间长度。
-
一个月后,系统会发送通知提示用户更改密码。
- 如果在规定的宽限期内没有更新密码,访问可能会被限制,直到用户设置了新密码。
// 模式
passwordHistory: [{ type: String }], // 用于存储最近三个密码哈希的数组
passwordUpdatedAt: { type: Date, default: Date.now }, // 密码最后更新的时间
// 你可以在登录时检查密码是否过期,或者通过定时任务来查找并通知用户有关过期密码的信息。
进入全屏模式 退出全屏
11. 实施强密码政策
强密码策略有助于防止未授权访问,并减少暴力破解、字典攻击和凭证填充的风险。
实现:
import validator from 'validator';
const password = 'User@1234';
if (
!validator.isStrongPassword(password, {
minLength: 8,
minLowercase: 1,
minUppercase: 1,
minNumbers: 1,
minSymbols: 1,
})
) {
throw new Error('密码强度不足,请确保它包含足够的字母、数字和符号。');
}
全屏,退出 全屏
12. 用户专属令牌撤销
这指的是在不影响其他用户的情况下,使某一用户的令牌失效的过程。
在这样的情况下很有用:
- 检测到账户的可疑活动
- 用户因安全需要请求撤销令牌
- 账户被停用或暂停
- 令牌被偷或泄露
它是怎么工作的:
- 令牌的结构:
令牌(比如 JWT)包含用户数据和令牌版本。令牌版本存储在数据库中,用于跟踪用户令牌的有效性。
- 令牌检查:
当接收到请求时,服务器会解码该令牌并比较令牌中的 token 版本与数据库中存储的版本号。如果版本号匹配,令牌有效;否则令牌无效。
- 令牌撤回:
要撤销某个用户的全部令牌,只需在数据库中将令牌版本递增。所有旧版本的令牌将自动失效。
实施:
// Schema: token_version INT DEFAULT 1
// JWT 生成
const createToken = (user) => {
const payload = { userId: user.id, tokenVersion: user.tokenVersion };
return jwt.sign(payload, SECRET_KEY, { expiresIn: '1h' });
};
// 令牌版本不匹配,无效
if (user.tokenVersion !== decoded.tokenVersion) {
throw new Error('令牌版本不匹配,无效');
}
// 要撤销,增加 tokenVersion
user.tokenVersion++;
切换到全屏模式 退出全屏
13. 跨源资源共享 (CORS) 设置
配置 CORS 以限制哪些来源可以访问您的 API。只有受信任的域名才能向您的后端服务发送请求。
为什么它很重要:
- 阻止未经授权的网站向您的服务器发起请求。
- 防范跨站请求伪造(CSRF)。
实施:
或
实施步骤:
或
实施部分:
导入 cors 模块并设置允许的源,如下所示:
import cors from 'cors';
app.use(
cors({
origin: 'https://your-frontend-domain.com',
credentials: true,
})
);
进入全屏模式,退出全屏
也可以看看《基于角色的访问控制与功能导向方法》([原文链接](https://dev.to/sushantrate/role-based-access-control-rbac-with-feature-centric-approach-oj5))
看看我的GitHub主页,在这里:<https://github.com/sushantrahate>
希望这份指南对你有帮助。祝你编写代码开心!😄
共同學習,寫下你的評論
評論加載中...
作者其他優質文章