在当今的网络环境中,安全性是任何 Web 应用的基础。HTTPS 通过加密通信保护数据传输,而安全实践则可以防御各种常见攻击。本章将详细介绍如何在 Node.js 应用中启用 HTTPS,并采取一系列安全措施保护你的应用和用户数据。
HTTPS(HTTP over SSL/TLS)在 HTTP 协议基础上增加了 SSL/TLS 加密层,提供以下保障:
现代浏览器会对未启用 HTTPS 的网站标记“不安全”,并且许多新特性(如 Service Workers、地理定位)仅支持 HTTPS。
要启用 HTTPS,你需要一个 SSL 证书。证书通常从证书颁发机构(CA)购买,但开发环境可以使用自签名证书。
使用 OpenSSL 工具生成自签名证书(需要安装 OpenSSL):
# 生成私钥
openssl genrsa -out key.pem 2048
# 生成证书签名请求(CSR)
openssl req -new -key key.pem -out csr.pem
# 生成自签名证书(有效期365天)
openssl x509 -req -days 365 -in csr.pem -signkey key.pem -out cert.pem
也可以一步生成:openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes。
注意:自签名证书在浏览器中会显示不安全,需要手动信任,仅用于开发测试。
生产环境应从受信任的 CA 获取证书,例如:
Let's Encrypt 的证书可以通过 certbot 工具自动获取和续期。详情见后文。
Node.js 内置了 https 模块,可以创建 HTTPS 服务器。你需要提供证书和私钥文件。
const https = require('https');
const fs = require('fs');
const path = require('path');
const options = {
key: fs.readFileSync(path.join(__dirname, 'key.pem')),
cert: fs.readFileSync(path.join(__dirname, 'cert.pem'))
};
const server = https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello HTTPS!');
});
server.listen(443, () => {
console.log('HTTPS server running on port 443');
});
注意:端口 443 是 HTTPS 默认端口,运行可能需要管理员权限。你也可以使用其他端口如 8443。
大多数应用使用 Express 框架。将 Express 应用作为请求处理函数传递给 HTTPS 服务器即可:
const express = require('express');
const https = require('https');
const fs = require('fs');
const app = express();
app.get('/', (req, res) => {
res.send('Hello Express over HTTPS!');
});
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
https.createServer(options, app).listen(443, () => {
console.log('HTTPS server running on port 443');
});
你也可以同时启动 HTTP 服务器,用于重定向到 HTTPS(见下节)。
通常我们希望所有 HTTP 请求都重定向到 HTTPS。可以使用中间件实现:
app.use((req, res, next) => {
if (!req.secure) {
return res.redirect('https://' + req.headers.host + req.url);
}
next();
});
如果应用运行在代理(如 Nginx)后面,需要设置信任代理,因为 req.secure 可能检测不到。可以在 Express 中启用信任代理:
app.enable('trust proxy');
然后检查 req.protocol 或 req.headers['x-forwarded-proto']。
Let's Encrypt 提供免费证书,并通过 ACME 协议自动化续期。常用的客户端是 certbot。
获取证书(以 Nginx 为例,但 Node.js 应用也可使用 standalone 插件):
sudo certbot certonly --standalone -d example.com
证书文件会生成在 /etc/letsencrypt/live/example.com/ 目录下,包含 privkey.pem(私钥)、fullchain.pem(证书链)等。然后在 Node.js 中读取这些文件:
const options = {
key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem')
};
设置 crontab 定时任务自动续期(certbot 会处理):
0 0 * * * /usr/bin/certbot renew --quiet
注意:续期后可能需要重启 Node.js 应用加载新证书,可以通过 --post-hook 实现。
除了 HTTPS,应用层面也需要实施多种安全措施。
Helmet 是一个 Express 中间件集合,通过设置各种 HTTP 头来增强安全性。它包含 15 个较小的中间件,如 helmet.hsts()、helmet.frameguard() 等。
安装:npm install helmet
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet()); // 使用默认配置
Helmet 默认会设置以下头部(部分):
Strict-Transport-Security(HSTS)X-Frame-Options 防止点击劫持X-Content-Type-Options 防止 MIME 嗅探Referrer-PolicyContent-Security-Policy(需配置)如果你的 API 需要被其他域名的前端访问,需要正确配置 CORS。可以使用 cors 中间件。
安装:npm install cors
const cors = require('cors');
app.use(cors()); // 允许所有来源(生产环境应限制)
限制特定来源:
app.use(cors({
origin: 'https://example.com'
}));
始终使用参数化查询或 ORM/ODM 来构建数据库查询。例如使用 MySQL 的预处理语句:
db.execute('SELECT * FROM users WHERE id = ?', [userId]);
避免拼接字符串:
// 危险!不要这样做
db.query(`SELECT * FROM users WHERE name = '${userInput}'`);
escape-html 库。Content-Security-Policy 头限制资源加载来源。xssFilter(已包含在默认配置中)。防止暴力破解和 DoS 攻击。可以使用 express-rate-limit 中间件。
安装:npm install express-rate-limit
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 每个IP限制100个请求
});
app.use('/api', limiter);
永远不要明文存储密码。使用 bcrypt 等算法哈希密码。
安装:npm install bcrypt
const bcrypt = require('bcrypt');
const saltRounds = 10;
// 哈希
const hash = await bcrypt.hash(password, saltRounds);
// 验证
const match = await bcrypt.compare(inputPassword, storedHash);
如果使用 session,确保配置安全:
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // 仅HTTPS
sameSite: 'strict'
}
}));
防止超大请求体消耗资源:
app.use(express.json({ limit: '10kb' }));
除了 Helmet,你还可以手动设置一些重要的头部:
DENY 或 SAMEORIGIN。nosniff 防止 MIME 嗅探。Helmet 已经提供了这些,你也可以手动配置:
app.use((req, res, next) => {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
res.setHeader('X-Frame-Options', 'DENY');
next();
});
生产环境通常将 Node.js 应用运行在内部端口,前面使用 Nginx 或 Apache 作为反向代理处理 SSL 和负载均衡。这样可以简化证书管理,并利用 Nginx 的高性能静态文件处理。
示例 Nginx 配置:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
此时 Node.js 应用不需要处理 SSL,但需要设置信任代理:app.enable('trust proxy');。
npm audit 检查安全漏洞。