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

如何将MSAL Node令牌缓存存入Postgres并解决应用重启清空缓存问题

重启缓存清空的根因

你当前代码的缓存逻辑存在时序问题:beforeCacheAccess中使用了回调式的fs.readFile,方法执行时不会等待文件读取完成,直接将fs.readFile的返回值(undefined)传给了deserialize方法,导致每次应用启动时MSAL都加载不到历史缓存,默认初始化空缓存,看起来就像缓存被清空了。
只要修复异步等待的逻辑,无论是使用原有的文件存储还是换成Postgres存储,都可以解决重启缓存丢失的问题。


实现Postgres存储令牌缓存的步骤

1. 前置准备

  • 安装PostgreSQL客户端依赖:npm install pg
  • 在Postgres中创建令牌缓存表,参考SQL:
CREATE TABLE IF NOT EXISTS msal_token_cache (
  id VARCHAR(255) PRIMARY KEY DEFAULT 'default_cache',
  cache_content TEXT NOT NULL,
  updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

如果你的应用需要支持多个机密客户端实例,可以新增client_id字段作为区分维度。

2. 替换原有缓存插件实现

把原来基于文件的cachePlugin替换成Postgres读写的版本,注意所有异步操作都要使用await等待完成,避免之前的时序问题。完整参考代码如下:

const { Pool } = require('pg');
const msal = require('@azure/msal-node');
const config = require('./你的配置文件路径');

// 初始化Postgres连接池
const pgPool = new Pool({
  host: config.PG_HOST,
  port: config.PG_PORT,
  user: config.PG_USER,
  password: config.PG_PASSWORD,
  database: config.PG_DATABASE
});

const beforeCacheAccess = async (cacheContext) => {
  try {
    // 从Postgres读取缓存
    const res = await pgPool.query('SELECT cache_content FROM msal_token_cache WHERE id = $1', ['default_cache']);
    if (res.rows.length > 0) {
      // 反序列化缓存内容到MSAL上下文
      cacheContext.tokenCache.deserialize(res.rows[0].cache_content);
    }
  } catch (err) {
    // 首次启动无缓存时可忽略错误,MSAL会初始化空缓存
    console.error('读取MSAL缓存失败:', err);
  }
};

const afterCacheAccess = async (cacheContext) => {
  if (cacheContext.cacheHasChanged) {
    try {
      const serializedCache = cacheContext.tokenCache.serialize();
      // 写入缓存到Postgres,不存在则插入,存在则更新
      await pgPool.query(`
        INSERT INTO msal_token_cache (id, cache_content, updated_at)
        VALUES ($1, $2, NOW())
        ON CONFLICT (id) DO UPDATE SET cache_content = $2, updated_at = NOW()
      `, ['default_cache', serializedCache]);
    } catch (err) {
      console.error('写入MSAL缓存失败:', err);
    }
  }
};

// 缓存插件
const cachePlugin = {
  beforeCacheAccess,
  afterCacheAccess
};

const msalConfig = {
  auth: {
    clientId: config.MS_CLIENT_ID,
    authority: config.AAD_ENDPOINT + 'consumers',
    clientSecret: config.MS_CLIENT_SECRET,
  },
  cache: {
    cachePlugin
  },
};
const scopes = ["Mail.ReadWrite", "Mail.Send"]
const cca = new msal.ConfidentialClientApplication(msalConfig);

额外注意事项
  • 生产环境建议给msal_token_cache表设置定期清理策略,清理过期的缓存条目,避免存储冗余内容
  • 如果你的应用是多租户部署,需要调整缓存表的主键规则,按租户+客户端ID维度存储不同的缓存
  • 确保Postgres连接池的配置符合你的应用并发需求,避免缓存读写出现连接瓶颈

内容的提问来源于stack exchange,提问作者Sunny G

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 11:39:03