Node.js中为API出站请求添加动态及定时过期Token头的方法
Great question! Let's break this down into two clear parts since you have two distinct token requirements—one that changes with every request, and another that’s cached with a 10-minute expiry. I’ll cover both common scenarios: using a popular HTTP client like Axios (most projects use this) and global interception for all outbound requests (if you need to cover every HTTP call in your app).
Scenario 1: Using Axios Interceptors (Recommended for Most Apps)
Axios has built-in request interceptors that let you modify outgoing requests before they’re sent. This is clean, maintainable, and perfect for most use cases.
1. Add a Per-Request Dynamic Token
This token changes with every request—think signatures based on request details, timestamps, or unique request IDs. We’ll generate it on the fly in the interceptor:
const axios = require('axios'); // Create a reusable Axios instance const apiClient = axios.create({ baseURL: 'https://your-api-base-url.com' }); // Request interceptor for dynamic per-request token apiClient.interceptors.request.use((config) => { // Generate your dynamic token here (replace with your actual logic) // Example: Use request method, URL, and timestamp to create a unique token const perRequestToken = generateDynamicToken(config.method, config.url, Date.now()); // Add the token to request headers config.headers['X-Per-Request-Token'] = perRequestToken; return config; }, (error) => { // Handle request errors return Promise.reject(error); }); // Example token generation function (customize this for your use case) function generateDynamicToken(method, url, timestamp) { // Could be an HMAC signature, JWT, or any custom logic return `${method.toLowerCase()}:${url}:${timestamp}:your-secure-secret`; }
2. Add a 10-Minute Expiring Cached Token
For this token, we’ll cache it and only refresh it when it’s about to expire. We’ll also handle concurrent requests to avoid duplicate token fetch calls:
let cachedExpiringToken = null; let tokenExpiryTime = 0; let tokenFetchPromise = null; // Prevents concurrent token requests // Extend the interceptor to handle the expiring token apiClient.interceptors.request.use(async (config) => { const now = Date.now(); // Check if token is missing or about to expire (add 10s buffer to avoid race conditions) if (!cachedExpiringToken || now >= tokenExpiryTime - 10000) { // Reuse an existing token fetch promise if one is already in flight if (!tokenFetchPromise) { tokenFetchPromise = fetchNewExpiringToken(); } try { cachedExpiringToken = await tokenFetchPromise; tokenExpiryTime = now + 600000; // 10 minutes in milliseconds } finally { tokenFetchPromise = null; // Reset after fetch completes } } // Add the cached token to headers config.headers['X-Expiring-Token'] = cachedExpiringToken; return config; }, (error) => { return Promise.reject(error); }); // Example function to fetch a new token from your auth service async function fetchNewExpiringToken() { const response = await axios.get('https://your-auth-service.com/api/token'); return response.data.token; }
Scenario 2: Global Interception of All HTTP/HTTPS Requests
If your app uses multiple HTTP clients or directly uses Node’s native http/https modules, you can override the core request methods to intercept all outbound requests.
const http = require('http'); const https = require('https'); // Cache for expiring token let cachedExpiringToken = null; let tokenExpiryTime = 0; let tokenFetchPromise = null; // Wrap the original request methods function interceptOutboundRequest(options, callback) { // Add per-request dynamic token const perRequestToken = generateDynamicToken(options.method || 'GET', options.path, Date.now()); options.headers = options.headers || {}; options.headers['X-Per-Request-Token'] = perRequestToken; // Handle expiring token logic const now = Date.now(); if (!cachedExpiringToken || now >= tokenExpiryTime - 10000) { // Use a promise to handle async token fetch without breaking the request flow return new Promise(async (resolve, reject) => { try { if (!tokenFetchPromise) { tokenFetchPromise = fetchNewExpiringToken(); } cachedExpiringToken = await tokenFetchPromise; tokenExpiryTime = now + 600000; tokenFetchPromise = null; options.headers['X-Expiring-Token'] = cachedExpiringToken; // Send the original request const req = options.protocol === 'https:' ? https.request(options, callback) : http.request(options, callback); resolve(req); } catch (err) { reject(err); } }); } else { options.headers['X-Expiring-Token'] = cachedExpiringToken; // Send the original request immediately return options.protocol === 'https:' ? https.request(options, callback) : http.request(options, callback); } } // Override native http.request const originalHttpRequest = http.request; http.request = function(options, callback) { return interceptOutboundRequest({ ...options, protocol: 'http:' }, callback); }; // Override native https.request const originalHttpsRequest = https.request; https.request = function(options, callback) { return interceptOutboundRequest({ ...options, protocol: 'https:' }, callback); }; // Reuse the same token generation and fetch functions from Scenario 1 function generateDynamicToken(method, url, timestamp) { return `${method.toLowerCase()}:${url}:${timestamp}:your-secure-secret`; } async function fetchNewExpiringToken() { return new Promise((resolve, reject) => { https.get('https://your-auth-service.com/api/token', (res) => { let data = ''; res.on('data', (chunk) => data += chunk); res.on('end', () => resolve(JSON.parse(data).token)); }).on('error', reject); }); }
Key Notes to Avoid Pitfalls
- Concurrent Request Handling: The
tokenFetchPromisevariable ensures that multiple requests don’t all hit your token service at once when the token expires—they’ll wait for the in-flight request to complete. - Token Refresh Buffer: Adding a 10-second buffer before the token expires ensures you don’t send a request with a token that’s about to expire mid-flight.
- Customization: Replace the token generation and fetch logic with your actual auth service requirements (e.g., OAuth2 client credentials, JWT refresh tokens).
内容的提问来源于stack exchange,提问作者niku

