如何将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
相关产品推荐
相关产品推荐

