高安全架构 学习笔记(第 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: [
' 不向用户暴露敏感错误信息',
' 记录详细的错误日志',
' 使用统一的错误处理',
' 配置自定义错误页面'
]
}