Node.js Express 安全性配置与防护措施
Node.js Express 安全性配置与防护措施
输入验证与过滤
在 Web 应用开发中,输入验证与过滤是第一道防线,能够有效防止恶意用户通过输入非法数据来攻击应用。
- 基本输入验证
在 Express 应用中,我们可以使用第三方库,如
joi
来进行输入验证。joi
是一个功能强大的 schema 描述语言和数据验证器。
首先安装 joi
:
npm install joi
假设我们有一个处理用户注册的路由,需要验证用户名、邮箱和密码:
const express = require('express');
const Joi = require('joi');
const app = express();
const userSchema = Joi.object({
username: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).required()
});
app.post('/register', (req, res) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
// 处理注册逻辑
res.send('Registration successful');
});
const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
在上述代码中,userSchema
定义了用户名、邮箱和密码的验证规则。validate
方法对请求体进行验证,如果验证失败,返回错误信息给客户端。
- SQL 注入过滤
如果应用使用 SQL 数据库,SQL 注入是一个严重的安全威胁。例如,在使用
mysql
库连接 MySQL 数据库时,使用参数化查询来防止 SQL 注入。
安装 mysql
:
npm install mysql
假设我们有一个查询用户信息的路由:
const express = require('express');
const mysql = require('mysql');
const app = express();
const connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test'
});
connection.connect();
app.get('/user/:id', (req, res) => {
const query = 'SELECT * FROM users WHERE id =?';
connection.query(query, [req.params.id], (error, results, fields) => {
if (error) throw error;
res.send(results);
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在上述代码中,?
是参数占位符,req.params.id
作为参数传入 connection.query
方法,这样可以防止恶意用户通过构造恶意的 id
参数来执行非法的 SQL 语句。
- XSS 过滤
跨站脚本攻击(XSS)是攻击者在网页中注入恶意脚本的攻击方式。我们可以使用
DOMPurify
库来过滤掉恶意的 HTML 和 JavaScript 代码。
安装 DOMPurify
:
npm install dompurify
假设我们有一个处理用户评论的路由:
const express = require('express');
const DOMPurify = require('dompurify');
const app = express();
app.post('/comment', (req, res) => {
const cleanComment = DOMPurify.sanitize(req.body.comment);
// 保存过滤后的评论到数据库
res.send('Comment saved successfully');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在上述代码中,DOMPurify.sanitize
方法对用户输入的评论进行过滤,确保不包含恶意脚本。
安全 HTTP 头配置
HTTP 头可以用来增强应用的安全性,设置一些安全相关的头信息可以有效防止多种攻击。
- Content - Security - Policy(CSP)
CSP 用于限制网页可以加载的资源来源,防止 XSS 攻击。在 Express 中,我们可以使用
helmet
库来设置 CSP 头。
安装 helmet
:
npm install helmet
配置 CSP:
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", 'https://code.jquery.com'],
styleSrc: ["'self'", 'https://maxcdn.bootstrapcdn.com']
}
}));
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在上述代码中,defaultSrc
设置为 'self'
,表示默认只允许从当前域加载资源。scriptSrc
除了当前域外,还允许从 https://code.jquery.com
加载脚本,styleSrc
除了当前域外,允许从 https://maxcdn.bootstrapcdn.com
加载样式。
- X - Frame - Options
X - Frame - Options
头用于防止点击劫持攻击,即防止页面被嵌入到其他页面的<frame>
、<iframe>
或<object>
中。
使用 helmet
设置 X - Frame - Options
:
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet.frameguard({ action: 'deny' }));
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
上述代码中,action: 'deny'
表示不允许页面被任何其他页面嵌入。
- X - XSS - Protection
X - XSS - Protection
头用于启用浏览器内置的 XSS 过滤器。虽然现代浏览器已经有了较好的 XSS 防护机制,但设置这个头可以进一步增强安全性。
使用 helmet
设置 X - XSS - Protection
:
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet.xssFilter());
app.get('/', (req, res) => {
res.send('Hello World');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
身份验证与授权
- 基于 Token 的身份验证
基于 Token 的身份验证是一种常用的身份验证方式,在 Express 应用中,我们可以使用
jsonwebtoken
库来实现。
安装 jsonwebtoken
:
npm install jsonwebtoken
假设我们有一个登录路由和一个需要身份验证的路由:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const secretKey = 'your-secret-key';
// 登录路由
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 验证用户名和密码
if (username === 'admin' && password === 'password') {
const token = jwt.sign({ username }, secretKey, { expiresIn: '1h' });
res.send({ token });
} else {
res.status(401).send('Invalid credentials');
}
});
// 需要身份验证的路由
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) return res.status(401).send('Access denied. No token provided.');
try {
const decoded = jwt.verify(token.replace('Bearer ', ''), secretKey);
req.user = decoded;
next();
} catch (error) {
res.status(400).send('Invalid token');
}
};
app.get('/protected', authenticateToken, (req, res) => {
res.send('This is a protected route');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在上述代码中,登录成功后生成一个 JWT Token,在需要身份验证的路由中,通过 authenticateToken
中间件验证 Token 的有效性。
- 授权 授权是在身份验证的基础上,确定用户是否有权限访问特定资源。例如,我们可以根据用户角色来进行授权。
假设我们在用户的 JWT Token 中包含了角色信息:
// 登录路由
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 验证用户名和密码
if (username === 'admin' && password === 'password') {
const token = jwt.sign({ username, role: 'admin' }, secretKey, { expiresIn: '1h' });
res.send({ token });
} else if (username === 'user' && password === 'password') {
const token = jwt.sign({ username, role: 'user' }, secretKey, { expiresIn: '1h' });
res.send({ token });
} else {
res.status(401).send('Invalid credentials');
}
});
// 授权中间件
const authorize = (role) => {
return (req, res, next) => {
if (req.user.role === role) {
next();
} else {
res.status(403).send('Forbidden');
}
};
};
// 只有管理员能访问的路由
app.get('/admin-only', authenticateToken, authorize('admin'), (req, res) => {
res.send('This is an admin - only route');
});
// 用户能访问的路由
app.get('/user-only', authenticateToken, authorize('user'), (req, res) => {
res.send('This is a user - only route');
});
在上述代码中,authorize
中间件根据用户的角色来决定是否允许访问特定路由。
安全的会话管理
- 会话存储
在 Express 应用中,我们可以使用
express - session
库来管理会话。对于会话存储,我们可以选择内存存储(适用于开发环境)或更安全的数据库存储,如 Redis。
安装 express - session
和 connect - redis
(用于 Redis 存储):
npm install express - session connect - redis
使用 Redis 存储会话:
const express = require('express');
const session = require('express - session');
const RedisStore = require('connect - redis')(session);
const redis = require('redis');
const app = express();
const client = redis.createClient();
app.use(session({
store: new RedisStore({ client }),
secret: 'your - secret - key',
resave: false,
saveUninitialized: true
}));
app.get('/', (req, res) => {
if (!req.session.views) {
req.session.views = 1;
} else {
req.session.views++;
}
res.send(`You have visited this page ${req.session.views} times`);
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在上述代码中,会话数据存储在 Redis 中,secret
用于加密会话 cookie。
- 会话 cookie 安全设置
为了增强会话 cookie 的安全性,我们可以设置一些属性,如
httponly
和secure
。httponly
防止 JavaScript 访问 cookie,secure
确保 cookie 只在 HTTPS 连接下传输。
app.use(session({
store: new RedisStore({ client }),
secret: 'your - secret - key',
resave: false,
saveUninitialized: true,
cookie: {
httponly: true,
secure: true,
maxAge: 1000 * 60 * 60 * 24 // 1天
}
}));
上述代码中,httponly
设置为 true
,secure
设置为 true
,同时设置了 cookie 的最大有效期为 1 天。
防止暴力破解攻击
- 限制登录尝试次数
为了防止暴力破解用户登录密码,我们可以限制用户在一定时间内的登录尝试次数。我们可以使用
express - rate - limit
库来实现。
安装 express - rate - limit
:
npm install express - rate - limit
const express = require('express');
const rateLimit = require('express - rate - limit');
const app = express();
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 5, // 15分钟内最多5次尝试
message: 'Too many login attempts. Please try again later.'
});
app.use(limiter);
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 验证用户名和密码
if (username === 'admin' && password === 'password') {
res.send('Login successful');
} else {
res.status(401).send('Invalid credentials');
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在上述代码中,limiter
限制了用户在 15 分钟内最多进行 5 次登录尝试,超过次数返回错误信息。
- 使用验证码
验证码是一种常用的防止暴力破解和机器人攻击的方法。我们可以使用
captcha - generator
库来生成验证码图片。
安装 captcha - generator
:
npm install captcha - generator
const express = require('express');
const Captcha = require('captcha - generator');
const app = express();
app.get('/captcha', (req, res) => {
const captcha = new Captcha(120, 40, 5);
req.session.captcha = captcha.text;
res.type('png').send(captcha.buffer);
});
app.post('/login', (req, res) => {
const { username, password, captcha } = req.body;
if (captcha.toLowerCase()!== req.session.captcha.toLowerCase()) {
return res.status(400).send('Invalid captcha');
}
// 验证用户名和密码
if (username === 'admin' && password === 'password') {
res.send('Login successful');
} else {
res.status(401).send('Invalid credentials');
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在上述代码中,/captcha
路由生成验证码图片并将验证码文本存储在会话中,/login
路由验证用户输入的验证码是否正确。
安全的部署与环境配置
- HTTPS 配置
在生产环境中,使用 HTTPS 是至关重要的,它可以加密传输的数据,防止中间人攻击。我们可以使用
https
模块结合 SSL/TLS 证书来配置 Express 应用。
假设我们已经有了 SSL/TLS 证书(cert.pem
和 key.pem
):
const express = require('express');
const https = require('https');
const fs = require('fs');
const app = express();
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
app.get('/', (req, res) => {
res.send('This is an HTTPS - enabled server');
});
https.createServer(options, app).listen(443, () => {
console.log('Server running on port 443 (HTTPS)');
});
在上述代码中,https.createServer
方法使用证书配置创建了一个 HTTPS 服务器。
- 环境变量与敏感信息管理 在开发和部署过程中,敏感信息,如数据库密码、API 密钥等,不应该硬编码在代码中。我们可以使用环境变量来管理这些信息。
在 .env
文件中设置环境变量:
DB_PASSWORD=your - database - password
SECRET_KEY=your - secret - key
使用 dotenv
库加载环境变量:
npm install dotenv
const express = require('express');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
const dbPassword = process.env.DB_PASSWORD;
const secretKey = process.env.SECRET_KEY;
// 使用环境变量进行数据库连接等操作
app.listen(3000, () => {
console.log('Server running on port 3000');
});
在上述代码中,dotenv.config()
加载 .env
文件中的环境变量,process.env
可以获取这些变量的值。
- 生产环境优化
在生产环境中,我们还可以进行一些优化来提高安全性和性能。例如,启用 gzip 压缩来减少传输数据的大小,使用
compression
库:
npm install compression
const express = require('express');
const compression = require('compression');
const app = express();
app.use(compression());
app.get('/', (req, res) => {
res.send('This is a compressed response');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
上述代码中,compression
中间件启用了 gzip 压缩,对响应数据进行压缩后再发送给客户端。同时,在生产环境中,应该关闭开发模式下的调试信息输出,避免泄露敏感信息。
通过以上全面的安全性配置与防护措施,可以显著提高 Node.js Express 应用的安全性,降低被攻击的风险,为用户提供更可靠和安全的服务。