导言
我们的目标是建坚不可摧的Node.js应用
一、Node.js常见安全隐患
Node.js因其高性能和易用性而广受欢迎,但默认配置下存在一些安全隐患:
- HTTP头部信息暴露 :可能泄露服务器技术栈
- 底裤没了
- 底裤没了
- 缺乏请求限制:易受DDoS攻击
- 输入验证不足:XSS和SQL注入风险
- 认证机制薄弱:会话劫持和CSRF攻击
二、常用三大安全中间件
1. Helmet - 您的HTTP头部卫士
Helmet通过设置 HTTP 头部增强安全性
ini
const helmet = require('helmet');
app.use(helmet());
核心防护功能:
- Content-Security-Policy: 设置内容安全策略,防止 XSS 攻击。
- Cross-Origin-Opener-Policy: 帮助隔离页面进程。
- Cross-Origin-Resource-Policy: 阻止跨域加载资源。
- Origin-Agent-Cluster: 更改进程隔离方式,使其基于源。
- Referrer-Policy: 控制 Referer 头。
- Strict-Transport-Security: 告诉浏览器优先使用 HTTPS。
- X-Content-Type-Options: 避免 MIME 嗅探。
- X-DNS-Prefetch-Control: 控制 DNS 预取。
- X-Download-Options: 强制下载保存(仅适用于 Internet Explorer)。
- X-Frame-Options: 防止点击劫持攻击。
- X-Permitted-Cross-Domain-Policies: 控制 Adobe 产品的跨域行为。
- X-Powered-By: 移除服务器信息头,防止简单攻击。
- X-XSS-Protection: 禁用浏览器的 XSS 过滤器。
生产环境建议:
php
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "images.example.com"]
}
},
hsts: {
maxAge: 63072000, // 2年
includeSubDomains: true,
preload: true
}
}));
2. Express Rate Limit - 流量控制阀门
防止暴力破解和DDoS攻击的利器:
php
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟窗口
max: 100, // 每个IP最多100个请求
message: '请求过于频繁,请15分钟后再试',
standardHeaders: true, // 返回标准RateLimit头部
legacyHeaders: false // 禁用非标准头部
});
app.use('/api/', apiLimiter);
多级限流策略:
php
// 登录接口更严格的限制
const authLimiter = rateLimit({
windowMs: 5 * 60 * 1000, // 5分钟
max: 5,
message: '尝试登录次数过多,请5分钟后再试'
});
app.use('/api/auth', authLimiter);
3. CORS - 跨域资源共享管理
精细控制跨域访问:
javascript
const cors = require('cors');
const corsOptions = {
origin: (origin, callback) => {
const allowedOrigins = ['https://example.com', 'https://admin.example.com'];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('不允许的跨域请求'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // 预检请求缓存24小时
};
app.use(cors(corsOptions));
三、认证与授权
1. Passport.js - 认证中间件
支持500+种认证策略的模块化系统:
javascript
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const JWTStrategy = require('passport-jwt').Strategy;
// 本地策略(用户名密码)
passport.use(new LocalStrategy(
{ usernameField: 'email' },
async (email, password, done) => {
try {
const user = await User.findOne({ email });
if (!user) return done(null, false);
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) return done(null, false);
return done(null, user);
} catch (err) {
return done(err);
}
}
));
// JWT策略
passport.use(new JWTStrategy({
jwtFromRequest: req => req.cookies.jwt,
secretOrKey: process.env.JWT_SECRET
}, (payload, done) => {
User.findById(payload.sub)
.then(user => done(null, user || false))
.catch(err => done(err));
}));
2. Bcrypt - 密码存储的最佳实践
永远不要明文存储密码(多加盐哈哈哈):
javascript
const bcrypt = require('bcrypt');
const saltRounds = 12; // 成本系数
// 密码哈希
async function hashPassword(password) {
return await bcrypt.hash(password, saltRounds);
}
// 密码验证
async function comparePassword(input, hash) {
return await bcrypt.compare(input, hash);
}
安全要点:
- 盐值(salt)自动生成并包含在哈希中
- 成本系数建议10-12(兼顾安全性和性能)
- 使用异步版本避免阻塞事件循环
3. JWT - 无状态认证方案
php
const jwt = require('jsonwebtoken');
const { v4: uuidv4 } = require('uuid');
// 生成令牌
function generateToken(user) {
return jwt.sign({
sub: user.id,
jti: uuidv4(), // 唯一标识符
iat: Math.floor(Date.now() / 1000), // 签发时间
role: user.role
}, process.env.JWT_SECRET, {
expiresIn: '1h',
algorithm: 'HS256'
});
}
// 验证中间件
function authenticate(req, res, next) {
const token = req.cookies.jwt;
if (!token) return res.status(401).send('需要认证');
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).send('无效令牌');
}
}
安全增强措施:
- 设置合理的过期时间
- 使用HTTPS传输
- 考虑实现令牌黑名单机制
- 避免在令牌中存储敏感信息
四、输入处理
1. Express Validator - 请求验证
less
const { body, validationResult } = require('express-validator');
app.post('/register', [
// 验证规则
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }).withMessage('密码至少8个字符'),
body('age').isInt({ min: 18 }).withMessage('必须年满18岁'),
body('username').trim().escape() // 清理输入
], (req, res) => {
// 检查验证结果
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// 处理有效请求
});
常用验证方法:
.isEmail()
- 验证邮箱格式.isLength()
- 验证长度.matches()
- 正则匹配.isURL()
- 验证URL.custom()
- 自定义验证逻辑
2. XSS防护组合拳
javascript
const xss = require('xss');
const sanitizeHtml = require('sanitize-html');
// 输入清理
function cleanInput(input) {
// 先转义特殊字符
let cleaned = xss(input);
// 然后清理HTML标签(保留安全标签)
cleaned = sanitizeHtml(cleaned, {
allowedTags: ['b', 'i', 'em', 'strong', 'a'],
allowedAttributes: {
'a': ['href', 'title']
},
allowedSchemes: ['http', 'https']
});
return cleaned;
}
// 使用示例
app.post('/comment', (req, res) => {
const cleanComment = cleanInput(req.body.comment);
// 存储清理后的内容
});
五、依赖安全
1. npm audit - 内置漏洞检查
bash
# 检查漏洞
npm audit
# 自动修复
npm audit fix
# 生产环境检查
npm audit --production
2. Snyk - 持续监控
bash
# 安装CLI
npm install -g snyk
# 测试项目
snyk test
# 监控项目
snyk monitor
# 创建持续集成测试
snyk wizard
集成到CI/CD:
yaml
# GitHub Actions示例
name: Security Scan
on: [push, pull_request]
jobs:
snyk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
六、高级安全策略
1. CSRF(Cross-site request forgery,跨站请求伪造)防护
javascript
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
// 先设置cookie解析
app.use(cookieParser());
// 然后启用CSRF保护
const csrfProtection = csrf({ cookie: true });
// 使用示例
app.get('/form', csrfProtection, (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
app.post('/process', csrfProtection, (req, res) => {
// 处理受保护的表单
});
前端配合:
xml
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<!-- 其他表单字段 -->
</form>
2. 安全日志记录
php
const winston = require('winston');
const { combine, timestamp, json } = winston.format;
// 创建安全日志记录器
const securityLogger = winston.createLogger({
level: 'info',
format: combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
json()
),
transports: [
new winston.transports.File({
filename: 'logs/security.log',
level: 'warn'
}),
new winston.transports.Console({
format: winston.format.simple()
})
]
});
// 记录安全事件
function logSecurityEvent(event) {
securityLogger.warn({
message: event.message,
type: event.type,
user: event.user,
ip: event.ip
});
}
七、实战,构建安全Express应用
php
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const { expressjwt: jwt } = require('express-jwt');
const bcrypt = require('bcrypt');
const { body, validationResult } = require('express-validator');
const xss = require('xss');
const winston = require('winston');
// 初始化应用
const app = express();
// 1. 基础安全中间件
app.use(helmet());
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
// 2. 认证中间件
app.use(jwt({
secret: process.env.JWT_SECRET,
algorithms: ['HS256'],
getToken: req => req.cookies.jwt
}).unless({
path: ['/login', '/register']
}));
// 3. 路由定义
app.post('/login',
[
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 })
],
async (req, res) => {
// 验证输入
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
// 清理输入
const email = xss(req.body.email);
const password = req.body.password; // 密码不需要XSS清理
// 查找用户
const user = await User.findOne({ email });
if (!user) {
logSecurityEvent({
message: '登录尝试使用不存在的邮箱',
type: 'AUTH_FAILURE',
ip: req.ip
});
return res.status(401).json({ error: '认证失败' });
}
// 验证密码
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
logSecurityEvent({
message: '登录尝试使用错误密码',
type: 'AUTH_FAILURE',
user: user.id,
ip: req.ip
});
return res.status(401).json({ error: '认证失败' });
}
// 生成令牌
const token = generateToken(user);
// 设置安全cookie
res.cookie('jwt', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 3600000 // 1小时
});
res.json({ success: true });
} catch (err) {
logSecurityEvent({
message: '登录处理错误',
type: 'AUTH_ERROR',
error: err.message,
ip: req.ip
});
res.status(500).json({ error: '服务器错误' });
}
}
);
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`安全服务器已启动,监听端口 ${PORT}`);
});
八、持续安全实践
-
定期依赖更新:
sqlnpm outdated npm update
-
安全头检查 :
使用SecurityHeaders.com扫描您的网站
-
自动化扫描:
- OWASP ZAP
- Nessus
- Burp Suite
-
安全审计:
bashnpm audit snyk test
-
监控和响应:
- 设置日志告警
- 建立安全事件响应流程
- 定期进行安全培训
结语
在构建应用的时候回来看看,保护好自己的应用安全