You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 07:50:30