为了实现你描述的场景,我们需要为第三方用户提供一种方法来创建请求签名,并在 Node.js 服务端验证该签名。以下是详细步骤,包括如何将 URL 和请求参数拼接成字符串进行签名,以及如何在服务器上验证这些签名。

项目设置

首先,确保你已经初始化了一个 npm 项目并安装了 Express:

npm init -y
npm install express

创建 API 服务

下面是 Node.js 代码示例,它展示了如何通过 SHA-256 验证 GET 和 POST 请求中的签名:

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

// 定义密钥用于签名
const SECRET_KEY = 'your_secret_key';

// 用于生成签名的函数
function generateSignature(data) {
  return crypto.createHmac('sha256', SECRET_KEY).update(data).digest('hex');
}

// 拼接 URL 和请求参数生成待签名的字符串
function createSignString(method, url, params) {
  const sortedParams = Object.keys(params).sort().map(key => `${key}=${params[key]}`).join('&');
  return `${method.toUpperCase()}|${url}|${sortedParams}`;
}

// 中间件:验证签名
function verifySignature(req, res, next) {
  const { signature } = req.query || req.body;
  const method = req.method;
  const url = req.originalUrl.split('?')[0]; // 去除查询参数
  const params = method === 'GET' ? req.query : req.body;

  if (!signature) {
    return res.status(400).json({ error: 'Missing signature' });
  }

  // 创建签名字符串
  const signString = createSignString(method, url, params);
  
  // 生成期望的签名
  const expectedSignature = generateSignature(signString);

  if (expectedSignature !== signature) {
    return res.status(403).json({ error: 'Invalid signature' });
  }

  next();
}

// 路由:受保护的 API 请求路径
app.use('/api/protected', verifySignature);

app.get('/api/protected', (req, res) => {
  res.json({ message: 'GET request signature verified successfully!' });
});

app.post('/api/protected', (req, res) => {
  res.json({ message: 'POST request signature verified successfully!' });
});

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

客户端请求示例

以下是一个生成签名并发送请求的客户端示例:

const crypto = require('crypto');

// 用于生成签名的密钥
const SECRET_KEY = 'your_secret_key';

// 签名生成函数
function generateSignature(data) {
  return crypto.createHmac('sha256', SECRET_KEY).update(data).digest('hex');
}

// 创建签名字符串
function createSignString(method, url, params) {
  const sortedParams = Object.keys(params).sort().map(key => `${key}=${params[key]}`).join('&');
  return `${method.toUpperCase()}|${url}|${sortedParams}`;
}

// 示例 GET 请求
function sendGetRequest() {
  const url = '/api/protected';
  const params = { param1: 'value1', param2: 'value2' };
  const signString = createSignString('GET', url, params);
  const signature = generateSignature(signString);

  const queryParams = new URLSearchParams({ ...params, signature }).toString();
  fetch(`http://localhost:3000${url}?${queryParams}`)
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));
}

// 示例 POST 请求
function sendPostRequest() {
  const url = '/api/protected';
  const params = { param3: 'value3', param4: 'value4' };
  const signString = createSignString('POST', url, params);
  const signature = generateSignature(signString);

  fetch(`http://localhost:3000${url}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ...params, signature })
  })
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));
}

sendGetRequest();
sendPostRequest();

流程说明

  1. 签名生成: 在客户端,将请求的方法、URL 和参数以预定义的格式拼接成字符串,然后通过 SHA-256 和共享密钥生成签名。

  2. 签名验证: 在服务器端,接收请求后,提取相同的信息,生成期望的签名,并与客户端提供的签名进行比较。

  3. 重要提示: 确保使用 HTTPS 以保护传输中的敏感信息和签名,并保障 SECRET_KEY 的安全性。