Appearance
认证安全
认证安全是 Web 应用安全的基础,涉及用户身份验证和会话管理。本指南将详细介绍如何实现安全的认证机制。
密码安全
密码策略
javascript
// 密码强度验证
function validatePassword(password) {
const minLength = 8;
const hasUpperCase = /[A-Z]/.test(password);
const hasLowerCase = /[a-z]/.test(password);
const hasNumbers = /\d/.test(password);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
return {
isValid: password.length >= minLength &&
hasUpperCase &&
hasLowerCase &&
hasNumbers &&
hasSpecialChar,
requirements: {
minLength: password.length >= minLength,
hasUpperCase,
hasLowerCase,
hasNumbers,
hasSpecialChar
}
};
}
// 使用示例
const passwordResult = validatePassword("MyPass123!");
console.log(passwordResult.isValid); // true
密码哈希
javascript
const bcrypt = require('bcrypt');
class PasswordService {
static async hashPassword(password, saltRounds = 12) {
return await bcrypt.hash(password, saltRounds);
}
static async verifyPassword(password, hashedPassword) {
return await bcrypt.compare(password, hashedPassword);
}
// 检查哈希是否需要重新生成(例如,盐值轮换)
static async needsRehash(hashedPassword, saltRounds = 12) {
return await bcrypt.getRounds(hashedPassword) < saltRounds;
}
}
// 使用示例
async function createUserWithSecurePassword(userData) {
const hashedPassword = await PasswordService.hashPassword(userData.password);
return await db.users.create({
...userData,
password: hashedPassword
});
}
会话管理
安全的会话实现
javascript
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
class SessionManager {
constructor(secret, options = {}) {
this.secret = secret;
this.options = {
expiresIn: '24h',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
...options
};
}
// 生成安全的会话令牌
generateSessionToken() {
return crypto.randomBytes(32).toString('hex');
}
// 创建 JWT 令牌
createToken(payload) {
return jwt.sign(payload, this.secret, {
expiresIn: this.options.expiresIn,
issuer: 'your-app',
audience: 'your-users'
});
}
// 验证令牌
verifyToken(token) {
try {
return jwt.verify(token, this.secret);
} catch (error) {
throw new Error('Invalid or expired token');
}
}
// 刷新令牌
refreshToken(oldToken) {
try {
const decoded = jwt.verify(oldToken, this.secret);
// 移除敏感信息,只保留必要信息
const payload = { userId: decoded.userId, role: decoded.role };
return this.createToken(payload);
} catch (error) {
throw new Error('Cannot refresh invalid token');
}
}
}
// Express 中间件示例
const sessionManager = new SessionManager(process.env.JWT_SECRET);
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.sendStatus(401);
}
try {
const user = sessionManager.verifyToken(token);
req.user = user;
next();
} catch (error) {
return res.status(403).json({ error: 'Invalid token' });
}
};
OAuth 和第三方认证
OAuth 2.0 安全实践
javascript
const crypto = require('crypto');
class OAuthSecurity {
// 生成 PKCE 代码挑战
static generatePKCE() {
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto
.createHash('sha256')
.update(verifier)
.digest('base64url');
return { verifier, challenge };
}
// 验证授权码
static async verifyAuthorizationCode(code, storedCode, codeVerifier = null) {
// 防止重放攻击
if (storedCode.used) {
throw new Error('Code already used');
}
// 验证时间戳
if (Date.now() - storedCode.createdAt > 10 * 60 * 1000) { // 10分钟过期
throw new Error('Code expired');
}
// 验证代码
if (code !== storedCode.code) {
throw new Error('Invalid code');
}
// 如果使用 PKCE,验证代码验证器
if (codeVerifier && storedCode.codeChallenge) {
const expectedChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
if (expectedChallenge !== storedCode.codeChallenge) {
throw new Error('Invalid code verifier');
}
}
// 标记代码为已使用
storedCode.used = true;
return storedCode;
}
}
多因素认证 (MFA)
TOTP 实现
javascript
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
class MFASecurity {
// 生成 MFA 密钥
static generateSecret(userEmail) {
return speakeasy.generateSecret({
name: `YourApp:${userEmail}`,
issuer: 'Your Application',
length: 32
});
}
// 验证 TOTP 代码
static verifyTOTP(token, secret, window = 1) {
return speakeasy.totp.verify({
secret: secret.base32,
encoding: 'base32',
token: token,
window: window // 容忍前后各一个时间窗口
});
}
// 生成 QR 码 URL
static generateQRCodeURL(secret, userEmail) {
return QRCode.toDataURL(secret.otpauth_url);
}
}
// 用户 MFA 设置
class UserMFASetup {
static async initiateMFA(userId) {
const secret = MFASecurity.generateSecret(`user-${userId}`);
// 存储密钥(加密存储)
await db.userMFA.create({
userId,
secret: encrypt(secret.base32, process.env.ENCRYPTION_KEY),
enabled: false
});
return {
qrCodeUrl: await MFASecurity.generateQRCodeURL(secret, `user-${userId}`),
backupCodes: this.generateBackupCodes()
};
}
static generateBackupCodes(count = 10) {
return Array.from({ length: count }, () =>
crypto.randomBytes(4).toString('hex')
);
}
static async enableMFA(userId, token) {
const mfaRecord = await db.userMFA.findOne({ userId });
const secret = decrypt(mfaRecord.secret, process.env.ENCRYPTION_KEY);
if (MFASecurity.verifyTOTP(token, { base32: secret })) {
await db.userMFA.update({
where: { userId },
data: { enabled: true }
});
return true;
}
return false;
}
}
认证安全最佳实践
防止暴力破解
javascript
const RateLimiter = require('rate-limiter-flexible');
// 登录速率限制
const loginLimiter = new RateLimiter.Memory({
points: 5, // 5 次尝试
duration: 900 // 15 分钟
});
class AuthenticationSecurity {
// 安全的登录实现
static async secureLogin(username, password) {
try {
// 检查速率限制
await loginLimiter.consume(username);
} catch (rejRes) {
throw new Error('Too many login attempts. Please try again later.');
}
// 验证凭据(总是执行相同的操作时间)
const user = await db.users.findOne({ email: username });
const isValid = user ?
await PasswordService.verifyPassword(password, user.password) :
await PasswordService.verifyPassword(password, '$2b$12$dummy'); // 防止时序攻击
if (!isValid) {
// 记录失败尝试
await this.recordFailedAttempt(username);
throw new Error('Invalid credentials');
}
// 重置失败计数
await this.resetFailedAttempts(username);
// 检查账户状态
if (user.isLocked) {
throw new Error('Account is locked');
}
// 生成安全令牌
const sessionManager = new SessionManager(process.env.JWT_SECRET);
const token = sessionManager.createToken({
userId: user.id,
email: user.email,
role: user.role
});
return { token, user: { id: user.id, email: user.email } };
}
static async recordFailedAttempt(username) {
// 实现失败尝试记录逻辑
const failedAttempts = await db.failedAttempts.count({
where: {
username,
timestamp: { gte: new Date(Date.now() - 15 * 60 * 1000) } // 15分钟内
}
});
if (failedAttempts >= 5) {
// 锁定账户
await db.users.update({
where: { email: username },
data: { isLocked: true }
});
}
}
static async resetFailedAttempts(username) {
// 重置失败尝试计数
await db.failedAttempts.deleteMany({
where: { username }
});
}
}
安全的注销实现
javascript
class SecureLogout {
static async logout(req, res) {
const token = req.headers.authorization?.split(' ')[1];
// 将令牌加入黑名单(如果使用黑名单机制)
if (token) {
await TokenBlacklist.add(token);
}
// 清除客户端 Cookie(如果使用)
res.clearCookie('sessionId', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict'
});
res.status(200).json({ message: 'Logged out successfully' });
}
// 刷新令牌轮换
static async rotateRefreshToken(oldRefreshToken) {
// 验证旧令牌
const decoded = jwt.verify(oldRefreshToken, process.env.REFRESH_TOKEN_SECRET);
// 检查令牌是否已被使用
const tokenRecord = await db.tokenBlacklist.findOne({
where: { token: oldRefreshToken }
});
if (tokenRecord) {
throw new Error('Token has been compromised');
}
// 将旧令牌加入黑名单
await TokenBlacklist.add(oldRefreshToken);
// 生成新令牌
return jwt.sign(
{ userId: decoded.userId },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: '7d' }
);
}
}
认证安全检查清单
- [ ] 使用强密码策略和哈希算法(如 bcrypt)
- [ ] 实施速率限制防止暴力破解
- [ ] 使用安全的会话管理(HTTPS、HttpOnly、Secure 标志)
- [ ] 实现多因素认证(MFA)
- [ ] 使用 PKCE 进行 OAuth 2.0 授权码流
- [ ] 实施适当的令牌生命周期管理
- [ ] 防止时序攻击(始终执行相同操作)
- [ ] 使用 CSRF 令牌保护表单
- [ ] 定期轮换密钥和令牌
- [ ] 记录和监控认证活动
通过实施这些认证安全措施,您可以大大增强应用程序的身份验证安全性。