安全, 隐私·
安全是 Web 的基石
没有安全,一切创新都无从谈起。在连接全球数十亿用户的 Web 生态系统中,安全问题可能导致数据泄露、身份被盗、财务损失,甚至威胁用户的人身安全。Web 安全不仅是技术问题,更是信任的基础。
OWASP(开放 Web 应用安全项目)每年发布 Top 10 Web 应用安全风险,为我们指明了需要重点关注的安全威胁。
OWASP Top 10 常见威胁
注入攻击
SQL 注入、命令注入等通过恶意输入执行未授权命令
认证失效
弱密码、会话管理不当导致的身份验证漏洞
XSS
跨站脚本攻击,在网页中注入恶意脚本
CSRF
跨站请求伪造,诱导用户执行非预期操作
安全配置错误
默认配置、不安全的设置等配置问题
不安全的反序列化
反序列化数据导致的远程代码执行
HTTPS 强制加密
HTTPS 是 Web 安全的基础,所有网站都应该使用:
# HTTP(不安全)
http://example.com
# HTTPS(安全)
https://example.com
强制 HTTPS 重定向:
# Nginx 配置
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 现代加密配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
}
// HSTS - 强制浏览器使用 HTTPS
// 通过响应头启用
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content Security Policy (CSP)
CSP 通过限制资源来源来防止 XSS 攻击:
<!-- 通过 HTTP 头设置 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline' https://cdn.example.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
font-src 'self' https://fonts.gstatic.com;
frame-ancestors 'none';
form-action 'self';">
// 报告模式(测试阶段)
const csp = `
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
report-uri /csp-report
`;
// 违规报告
app.post('/csp-report', (req, res) => {
console.log('CSP 违规:', req.body);
});
防止 XSS 攻击
跨站脚本攻击是最常见的 Web 安全威胁之一:
// ❌ 错误:直接插入用户输入
div.innerHTML = userInput;
// ✅ 正确:转义用户输入
div.textContent = userInput;
// 使用 DOMSanitizer
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userInput);
div.innerHTML = clean;
// 使用模板引擎自动转义
// React
<div>{userInput}</div>
// Vue
<div>{{ userInput }}</div>
// HTTPOnly Cookie 防止窃取
app.use(session({
secret: 'secret-key',
cookie: {
httpOnly: true, // JavaScript 无法访问
secure: true, // 仅通过 HTTPS 传输
sameSite: 'strict', // 防止 CSRF
maxAge: 3600000
}
}));
防止 CSRF 攻击
跨站请求伪造攻击诱导用户执行非预期操作:
// 服务端生成 CSRF Token
const crypto = require('crypto');
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = crypto.randomBytes(32).toString('hex');
}
res.locals.csrfToken = req.session.csrfToken;
next();
});
// 表单中包含 Token
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="{{csrfToken}}">
<input type="text" name="amount">
<button type="submit">转账</button>
</form>
// 验证 Token
app.post('/transfer', (req, res) => {
if (req.body.csrf_token !== req.session.csrfToken) {
return res.status(403).send('CSRF 验证失败');
}
// 处理转账
});
// SameSite Cookie 防止 CSRF
app.use(session({
cookie: {
sameSite: 'strict' // 或 'lax'
}
}));
安全的认证和授权
正确处理用户身份验证:
// 使用强哈希算法(bcrypt)
import bcrypt from 'bcryptjs';
// 注册时哈希密码
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
// 验证密码
const isValid = await bcrypt.compare(inputPassword, hashedPassword);
// 使用 JWT 进行身份验证
import jwt from 'jsonwebtoken';
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
// 验证 Token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 实施速率限制
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 分钟
max: 100 // 最多 100 次请求
});
app.use('/api/auth', limiter);
数据验证和清理
始终验证和清理用户输入:
// 使用 Zod 进行数据验证
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email(),
username: z.string().min(3).max(20),
password: z.string().min(8).regex(/[A-Z]/).regex(/[0-9]/),
age: z.number().min(13).max(120)
});
const result = userSchema.safeParse(userData);
if (!result.success) {
return res.status(400).json({
errors: result.error.errors
});
}
// 参数化查询防止 SQL 注入
// ❌ 错误
const query = `SELECT * FROM users WHERE id = '${userId}'`;
// ✅ 正确
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId]);
安全头设置
使用安全相关的 HTTP 头:
// Express 安全头
app.use(helmet()); // 包含多个安全头
// 或手动设置
app.use((req, res, next) => {
// 防止点击劫持
res.setHeader('X-Frame-Options', 'DENY');
// 防止 MIME 类型嗅探
res.setHeader('X-Content-Type-Options', 'nosniff');
// XSS 防护
res.setHeader('X-XSS-Protection', '1; mode=block');
// 引用者策略
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// 权限策略
res.setHeader('Permissions-Policy', 'geolocation=()');
next();
});
依赖安全
确保使用的第三方库是安全的:
# 使用 npm audit 检查漏洞
npm audit
# 自动修复
npm audit fix
# 使用 Snyk 进行深度检查
npx snyk test
# 使用 Dependabot 自动更新依赖
# 在 GitHub 上启用
监控和日志
持续监控安全事件:
// 安全日志
import winston from 'winston';
const securityLogger = winston.createLogger({
level: 'info',
transports: [
new winston.transports.File({
filename: 'security.log',
level: 'info'
})
]
});
// 记录可疑活动
app.use((req, res, next) => {
if (req.body.password) {
securityLogger.warn('敏感数据传输', {
ip: req.ip,
url: req.url,
userAgent: req.get('user-agent')
});
}
next();
});
// 检测异常活动
const ipRequests = new Map();
app.use((req, res, next) => {
const ip = req.ip;
const count = (ipRequests.get(ip) || 0) + 1;
if (count > 1000) {
securityLogger.error('可疑活动', { ip, count });
return res.status(429).send('请求过多');
}
ipRequests.set(ip, count);
next();
});
安全检查清单
传输加密
使用 HTTPS,配置 HSTS
输入验证
验证所有用户输入,防止注入
输出编码
转义所有动态内容,防止 XSS
认证授权
强密码、多因素认证、最小权限
数据保护
敏感数据加密、安全存储
监控审计
日志记录、异常检测
总结
Web 安全是一个持续的过程,需要在设计、开发、测试和部署的每个环节都给予关注。没有绝对安全的系统,但通过遵循最佳实践、使用安全工具、保持警惕,我们可以大大降低安全风险。
记住,安全不是一次性的任务,而是一种思维方式。每一个代码决策、每一个配置选项,都要考虑其安全影响。保护用户数据和隐私,是我们每个开发者的责任。