Skip to content
On this page

认证安全

认证安全是 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 令牌保护表单
  • [ ] 定期轮换密钥和令牌
  • [ ] 记录和监控认证活动

通过实施这些认证安全措施,您可以大大增强应用程序的身份验证安全性。