1. 当 API 网关成为系统的门卫
在微服务架构中,API 网关就像小区的保安亭,承担着流量调度、权限验证、安全检查等职责。但如果没有恰当的安全防护措施,这个"门卫"反而会成为攻击者的突破口。本文将聚焦三种核心防御手段:JWT 身份验证、请求签名校验和防重放攻击,通过完整示例展示如何用 Node.js 构建安全的 API 网关。
2. JWT 验证:你的数字身份证
2.1 为什么选择 JWT?
JSON Web Token 作为现代认证方案,具备无状态、轻量化、跨语言三大优势。其结构分为三部分:
Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywiaWF0IjoxNjE5ODQ2NDAwfQ.7Zb7wj6Jk9h8UQ4V1Y2cYf1m6pLx3qoKv0Jb2m5e7F0
2.2 Node.js 实现示例
技术栈:Express + jsonwebtoken + express-jwt
const express = require('express');
const jwt = require('jsonwebtoken');
const { expressjwt: ejwt } = require("express-jwt");
const app = express();
const SECRET = 'your_256bit_secret'; // 实际生产环境应使用环境变量存储
// 签发令牌端点
app.post('/login', (req, res) => {
// 模拟用户验证成功后签发令牌
const token = jwt.sign(
{ userId: 123, role: 'admin' }, // 载荷数据
SECRET,
{ expiresIn: '2h' } // 有效期设置
);
res.json({ token });
});
// 验证中间件
const jwtAuth = ejwt({
secret: SECRET,
algorithms: ['HS256'], // 明确指定加密算法
requestProperty: 'auth' // 将解码数据挂载到 req.auth
}).unless({ path: ['/login'] }); // 白名单设置
app.use(jwtAuth);
// 受保护端点
app.get('/profile', (req, res) => {
console.log(req.auth.userId); // 通过中间件获取用户信息
res.send('敏感数据');
});
app.listen(3000);
2.3 注意事项
- 密钥管理:切忌硬编码密钥,应使用 KMS 或环境变量
- 算法安全:禁用
none
算法,推荐 HS256/RS256 - 令牌回收:通过黑名单机制处理特殊注销场景
3. 请求签名:防篡改的指纹锁
3.1 签名工作原理
通过 HMAC 算法生成请求特征码,防止传输过程中被修改。典型签名流程:
- 客户端生成时间戳
- 将参数按规则排序拼接
- 使用密钥生成签名
- 将签名和时间戳加入请求头
3.2 实现方案
技术栈:Express + crypto
const crypto = require('crypto');
// 签名生成函数(客户端使用)
function createSignature(secret, params) {
const sortedParams = Object.keys(params)
.sort()
.map(k => `${k}=${params[k]}`)
.join('&');
return crypto
.createHmac('sha256', secret)
.update(sortedParams)
.digest('hex');
}
// 中间件验证(服务端)
function verifySignature(req, res, next) {
const clientSign = req.headers['x-api-sign'];
const timestamp = req.headers['x-api-timestamp'];
const params = { ...req.query, ...req.body };
// 时间有效性验证(防止重放攻击)
if (Date.now() - parseInt(timestamp) > 300000) {
return res.status(401).send('请求已过期');
}
// 重新计算签名
const serverSign = createSignature(process.env.API_SECRET, params);
if (serverSign !== clientSign) {
return res.status(401).send('签名校验失败');
}
next();
}
// 在 Express 中使用
app.post('/payment', verifySignature, (req, res) => {
// 处理支付逻辑
});
3.3 关键要点
- 时间窗口:建议 5 分钟有效期
- 参数处理:GET/POST 参数需要统一处理
- 密钥分发:采用客户端注册机制动态分配密钥
4. 防重放攻击:让每个请求独一无二
4.1 双重防御机制
- 时间戳校验:拒绝超过时间窗口的请求
- Nonce 验证:确保每个请求的唯一性
4.2 完整实现
技术栈:Express + Redis
const redis = require('redis');
const client = redis.createClient();
// 生成唯一 nonce(客户端)
function generateNonce() {
return crypto.randomBytes(16).toString('hex');
}
// 防重放中间件
async function replayProtect(req, res, next) {
const nonce = req.headers['x-nonce'];
const timestamp = req.headers['x-timestamp'];
// 时间窗口验证(示例设置60秒)
if (Date.now() - parseInt(timestamp) > 60000) {
return res.status(401).send('请求超时');
}
// Redis 校验 nonce 唯一性
const exist = await client.exists(nonce);
if (exist === 1) {
return res.status(401).send('重复请求');
}
// 记录 nonce 并设置过期时间
await client.set(nonce, 'used', 'EX', 65); // 略大于时间窗口
next();
}
// 使用示例
app.post('/transfer', replayProtect, (req, res) => {
// 处理转账请求
});
4.3 优化方向
- 分布式存储:使用 Redis 集群保证一致性
- 性能优化:采用内存缓存+异步持久化策略
- Nonce 生成:使用高强度随机算法
5. 技术联动:构建纵深防御体系
将三项技术组合使用,形成多层防护:
// 复合中间件示例
app.post('/vip-operate',
ejwt({ secret: SECRET }), // 第一层:身份验证
verifySignature, // 第二层:防篡改
replayProtect, // 第三层:防重放
(req, res) => { // 业务逻辑
// 处理核心业务
}
);
6. 应用场景分析
6.1 典型应用
- 金融交易系统:请求签名+防重放
- IoT 设备接入:JWT 设备身份认证
- 开放平台 API:签名验证保证第三方调用安全
6.2 技术选型对比
方案 | 优点 | 缺点 |
---|---|---|
JWT | 无状态、易于扩展 | 令牌吊销困难 |
请求签名 | 防篡改能力极强 | 客户端实现复杂度高 |
时间戳+Nonce | 有效防止重放 | 需要存储状态 |
7. 安全实践建议
- 密钥轮换机制:每季度更新签名密钥
- 防御组合策略:根据业务场景选择技术组合
- 监控预警:记录异常请求并触发告警
- 漏洞扫描:定期进行安全渗透测试
8. 总结
API 网关的安全防护需要纵深防御体系:JWT 确保用户身份合法,请求签名保证数据完整,时间戳+Nonce 防止请求被重复利用。三者组合使用能有效应对大多数 API 安全威胁,但需要根据实际业务场景进行参数调优和安全加固。