{T}

高安全架构 学习笔记(第 14 部分)

6.4 CSRF 防护

javascript
// CSRF 防护
const crypto = require('crypto')

class CSRFProtection {
  constructor() {
    this.secret = 'csrf-secret-key'
    this.tokenExpiry = 3600000  // 1小时
  }
  
  // 生成 CSRF Token
  generateToken(sessionId) {
    const timestamp = Date.now()
    const data = `${sessionId}:${timestamp}`
    
    const hash = crypto
      .createHmac('sha256', this.secret)
      .update(data)
      .digest('hex')
    
    const token = Buffer.from(`${data}:${hash}`).toString('base64')
    
    return {
      token,
      expiresAt: timestamp + this.tokenExpiry
    }
  }
  
  // 验证 CSRF Token
  verifyToken(token, sessionId) {
    try {
      const decoded = Buffer.from(token, 'base64').toString('utf8')
      const [tokenSessionId, timestamp, hash] = decoded.split(':')
      
      // 验证会话ID
      if (tokenSessionId !== sessionId) {
        return { valid: false, reason: '会话ID不匹配' }
      }
      
      // 验证时间戳
      if (Date.now() > parseInt(timestamp) + this.tokenExpiry) {
        return { valid: false, reason: 'Token 已过期' }
      }
      
      // 验证签名
      const data = `${tokenSessionId}:${timestamp}`
      const expectedHash = crypto
        .createHmac('sha256', this.secret)
        .update(data)
        .digest('hex')
      
      if (hash !== expectedHash) {
        return { valid: false, reason: '签名验证失败' }
      }
      
      return { valid: true }
    } catch (error) {
      return { valid: false, reason: 'Token 格式错误' }
    }
  }
  
  // CSRF 防护中间件
  middleware() {
    return (req, res, next) => {
      // 只检查修改操作
      const unsafeMethods = ['POST', 'PUT', 'DELETE', 'PATCH']
      
      if (unsafeMethods.includes(req.method)) {
        const token = req.body._csrf || 
                      req.get('X-CSRF-Token') ||
                      req.query._csrf
        
        const sessionId = req.session.id
        
        if (!token) {
          return res.status(403).json({
            error: 'CSRF Token Missing',
            message: '缺少 CSRF Token'
          })
        }
        
        const result = this.verifyToken(token, sessionId)
        
        if (!result.valid) {
          return res.status(403).json({
            error: 'CSRF Token Invalid',
            message: result.reason
          })
        }
      }
      
      next()
    }
  }
  
  // 生成 Token 路由
  tokenRoute() {
    return (req, res) => {
      const sessionId = req.session.id
      const { token, expiresAt } = this.generateToken(sessionId)
      
      res.json({
        csrfToken: token,
        expiresAt: new Date(expiresAt).toISOString()
      })
    }
  }
}

// 使用示例
const csrf = new CSRFProtection()

// 获取 CSRF Token
app.get('/api/csrf-token', csrf.tokenRoute())

// 应用 CSRF 防护
app.use(csrf.middleware())

// 受保护的路由
app.post('/api/update-profile', (req, res) => {
  // CSRF 验证通过
  res.json({ message: '更新成功' })
})

七、安全最佳实践

7.1 安全编码规范

javascript
// 安全编码清单
const securityBestPractices = {
  // 输入验证
  inputValidation: [
    ' 验证所有用户输入',
    ' 使用白名单而非黑名单',
    ' 验证数据类型、长度、格式',
    ' 对文件上传进行严格限制'
  ],
  
  // 输出编码
  outputEncoding: [
    ' 对输出进行 HTML 编码',
    ' 对 URL 参数进行编码',
    ' 对 JavaScript 数据进行编码',
    ' 设置正确的 Content-Type'
  ],
  
  // 身份认证
  authentication: [
    ' 使用强密码策略',
    ' 实施多因素认证',
    ' 限制登录尝试次数',
    ' 使用安全的密码存储(bcrypt)'
  ],
  
  // 会话管理
  sessionManagement: [
    ' 使用安全的 Session ID',
    ' 设置 Cookie 的 HttpOnly 和 Secure 标志',
    ' 实施会话超时',
    ' 登出时销毁会话'
  ],
  
  // 访问控制
  accessControl: [
    ' 实施最小权限原则',
    ' 验证每个请求的权限',
    ' 避免直接对象引用',
    ' 记录访问日志'
  ],
  
  // 错误处理
  errorHandling: [
    ' 不向用户暴露敏感错误信息',
    ' 记录详细的错误日志',
    ' 使用统一的错误处理',
    ' 配置自定义错误页面'
  ]
}