如何在Node.js应用中持久化认证状态?
如何在Node.js后端缓存第三方API的认证令牌以避免重复请求
针对你的需求,避免每次请求都调用第三方认证接口,可通过以下几种方案存储认证状态,按场景优先级推荐:
1. 内存缓存(适合学习/单实例部署)
这是最简单的实现方式,将令牌和过期时间存在模块级变量中,每次请求前先检查令牌是否有效,无效再重新获取。
修改后的代码示例:
const axios = require("axios"); const AUTH_URL = process.env.AUTH_URL; const REPORT_BASE_URL = process.env.REPORT_BASE_URL; const X_API_KEY = process.env.X_API_KEY; // 内存缓存令牌与过期时间 let cachedIdToken = null; let tokenExpiresAt = null; // 获取有效令牌的工具函数 const getValidIdToken = async () => { // 检查令牌是否存在且未过期 if (cachedIdToken && Date.now() < tokenExpiresAt) { return cachedIdToken; } // 重新发起认证请求 const authPayload = JSON.stringify({ // ... 填充你的认证参数 }); const config = { method: "post", url: AUTH_URL, headers: { // ... 填充认证请求所需的头部信息 }, data: authPayload }; try { const { data } = await axios(config); cachedIdToken = data.idToken; // 根据第三方API返回的有效期设置过期时间(默认3600秒) tokenExpiresAt = Date.now() + (data.expiresIn || 3600) * 1000; return cachedIdToken; } catch (error) { console.error("认证请求失败:", error); throw error; // 抛出错误让上层处理 } }; const getCompanies = async (req, res) => { const { full_text_query } = req.query; if (!full_text_query) { return res.status(400).send("No full_text_query parameter provided"); } try { const idToken = await getValidIdToken(); const { data } = await axios.get( `${REPORT_BASE_URL}/companies?full_text_query=${full_text_query}`, { headers: { "x-api-key": X_API_KEY, Accept: "application/json", authorization: idToken, }, } ); res.status(200).json(data); } catch (error) { console.error("获取公司数据失败:", error); res.status(500).send("获取数据失败"); } }; module.exports = { getCompanies, };
优缺点:实现简单无额外依赖,但服务重启或多实例部署时,每个实例都需要重新认证,令牌无法跨实例共享。
2. 外部缓存服务(适合生产/多实例部署)
如果你的服务是多实例部署,或者需要持久化缓存避免重启后重新认证,可以使用Redis这类外部缓存服务。
示例代码(基于Redis):
const axios = require("axios"); const { createClient } = require("redis"); const AUTH_URL = process.env.AUTH_URL; const REPORT_BASE_URL = process.env.REPORT_BASE_URL; const X_API_KEY = process.env.X_API_KEY; // 初始化Redis客户端 const redisClient = createClient({ url: process.env.REDIS_URL // 配置Redis连接地址,如redis://localhost:6379 }); redisClient.connect().catch(console.error); const getValidIdToken = async () => { // 从Redis获取缓存的令牌和过期时间 const cachedToken = await redisClient.get("id_token"); const expiresAt = await redisClient.get("token_expires_at"); if (cachedToken && Date.now() < parseInt(expiresAt)) { return cachedToken; } // 重新认证 const authPayload = JSON.stringify({ // ... 填充你的认证参数 }); const config = { method: "post", url: AUTH_URL, headers: { // ... 填充认证请求所需的头部信息 }, data: authPayload }; try { const { data } = await axios(config); const idToken = data.idToken; const expiresIn = data.expiresIn || 3600; const expiresAt = Date.now() + expiresIn * 1000; // 将令牌存入Redis,设置与令牌一致的过期时间 await redisClient.setEx("id_token", expiresIn, idToken); await redisClient.setEx("token_expires_at", expiresIn, expiresAt.toString()); return idToken; } catch (error) { console.error("认证请求失败:", error); throw error; } }; // getCompanies函数逻辑与内存缓存版本一致 const getCompanies = async (req, res) => { const { full_text_query } = req.query; if (!full_text_query) { return res.status(400).send("No full_text_query parameter provided"); } try { const idToken = await getValidIdToken(); const { data } = await axios.get( `${REPORT_BASE_URL}/companies?full_text_query=${full_text_query}`, { headers: { "x-api-key": X_API_KEY, Accept: "application/json", authorization: idToken, }, } ); res.status(200).json(data); } catch (error) { console.error("获取公司数据失败:", error); res.status(500).send("获取数据失败"); } }; module.exports = { getCompanies, };
优缺点:支持多实例共享缓存,可持久化避免重启丢失,但需要额外部署维护Redis服务。
3. Axios拦截器自动处理(适合多接口场景)
如果你的后端有多个接口需要调用第三方API,可以封装Axios实例,通过拦截器自动处理令牌的检查与刷新,避免在每个接口中重复编写逻辑。
示例代码:
const axios = require("axios"); const AUTH_URL = process.env.AUTH_URL; const REPORT_BASE_URL = process.env.REPORT_BASE_URL; const X_API_KEY = process.env.X_API_KEY; // 创建第三方API专用的Axios实例 const apiClient = axios.create({ baseURL: REPORT_BASE_URL, headers: { "x-api-key": X_API_KEY, Accept: "application/json" } }); let cachedIdToken = null; let tokenExpiresAt = null; let isRefreshingToken = false; let refreshSubscribers = []; // 刷新令牌函数 const refreshIdToken = async () => { const authPayload = JSON.stringify({ // ... 填充你的认证参数 }); const config = { method: "post", url: AUTH_URL, headers: { // ... 填充认证请求所需的头部信息 }, data: authPayload }; const { data } = await axios(config); cachedIdToken = data.idToken; tokenExpiresAt = Date.now() + (data.expiresIn || 3600) * 1000; return cachedIdToken; }; // 请求拦截器:自动添加令牌,过期时自动刷新 apiClient.interceptors.request.use(async (config) => { if (!cachedIdToken || Date.now() >= tokenExpiresAt) { if (isRefreshingToken) { // 如果正在刷新令牌,等待刷新完成后再继续请求 return new Promise((resolve) => { refreshSubscribers.push((token) => { config.headers.authorization = token; resolve(config); }); }); } isRefreshingToken = true; try { const newToken = await refreshIdToken(); config.headers.authorization = newToken; // 通知所有等待的请求 refreshSubscribers.forEach(callback => callback(newToken)); refreshSubscribers = []; } catch (error) { throw error; } finally { isRefreshingToken = false; } } else { config.headers.authorization = cachedIdToken; } return config; }); const getCompanies = async (req, res) => { const { full_text_query } = req.query; if (!full_text_query) { return res.status(400).send("No full_text_query parameter provided"); } try { // 直接使用封装好的apiClient,无需手动处理令牌 const { data } = await apiClient.get(`/companies?full_text_query=${full_text_query}`); res.status(200).json(data); } catch (error) { console.error("获取公司数据失败:", error); res.status(500).send("获取数据失败"); } }; module.exports = { getCompanies, };
优缺点:自动化程度高,代码复用性强,适合多个接口需要认证的场景,但逻辑相对复杂一些。
内容的提问来源于stack exchange,提问作者r-iliass
相关产品推荐
相关产品推荐

