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 算法生成请求特征码,防止传输过程中被修改。典型签名流程:

  1. 客户端生成时间戳
  2. 将参数按规则排序拼接
  3. 使用密钥生成签名
  4. 将签名和时间戳加入请求头

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 双重防御机制

  1. 时间戳校验:拒绝超过时间窗口的请求
  2. 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. 安全实践建议

  1. 密钥轮换机制:每季度更新签名密钥
  2. 防御组合策略:根据业务场景选择技术组合
  3. 监控预警:记录异常请求并触发告警
  4. 漏洞扫描:定期进行安全渗透测试

8. 总结

API 网关的安全防护需要纵深防御体系:JWT 确保用户身份合法,请求签名保证数据完整,时间戳+Nonce 防止请求被重复利用。三者组合使用能有效应对大多数 API 安全威胁,但需要根据实际业务场景进行参数调优和安全加固。