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

Express.js使用Azure托管身份无法在令牌过期后刷新访问令牌

问题:Azure托管身份令牌过期后无法刷新,导致Express应用无法连接数据库

我有一个Express.js应用,通过Azure托管身份连接Azure MySQL数据库。server.js文件导入并调用setupConnectionPool函数,可成功获取访问令牌并连接数据库。但24小时令牌过期后,我尝试的所有令牌刷新方案均未奏效。

我试过使用setInterval和setTimeout在令牌过期时重新调用setupConnectionPool,也尝试通过判断accessToken.expiresOnTimestamp < Date.now()来处理,但都没有成功。这是客户项目的最后一个待解决问题,恳请提供解决思路!

const mssql = require('mssql');
require('dotenv').config();
const { DefaultAzureCredential } = require("@azure/identity");

async function setupConnectionPool() {
    try {
      console.log(process.env.AZURE_DEV_MI_CLIENT_ID)
      const credential = new DefaultAzureCredential({
        managedIdentityClientId: process.env.AZURE_DEV_MI_CLIENT_ID,
      });
      console.log('credentialll', credential)
      let accessToken = await credential.getToken("https://database.windows.net/.default");
      console.log('access token', accessToken)

      const refreshTime = accessToken.expiresOnTimestamp - Date.now();
      console.log('refreshTime', refreshTime)
      const refreshToken = async () => {
        if(pool) {
          console.log('close pool')
          await pool.close();
        }
        console.log('refresh token func call')
        try {
          console.log('try setupconnectionpool')
          await setupConnectionPool();
        } catch(e) {
          console.log('catch e', e)
        }
      }

      setTimeout(() => {
        console.log('Get refresh token - 10000');
        refreshToken();
      }, (refreshTime - 10000))

      const config = {
        user: "",
        password: "",
        server: process.env.DB_SERVER,
        authentication: {
          type: "azure-active-directory-access-token",
          options: {
            token: accessToken.token,
          },
        },
        options: {
          database: process.env.DB_NAME,
          encrypt: true,
          port: 1433,
        },
      };
      const pool = new mssql.ConnectionPool(config);
      await pool.connect();
      console.log('pool', pool);
      return pool;
    } catch (error) {
      console.error("Error setting up connection pool:", error);
      throw error;
    }
}

module.exports = { setupConnectionPool }

核心问题分析

你的代码存在几个关键问题导致令牌刷新失败:

  • 变量作用域错误:refreshToken函数里引用的pool是setupConnectionPool内部的局部变量,刷新逻辑无法正确访问到当前的连接池实例,导致旧池无法关闭、新池无法替换。
  • 实例未全局同步:重新调用setupConnectionPool生成的新池,没有同步到业务代码使用的实例上,业务仍在使用带过期令牌的旧池。
  • 无重试容错机制:刷新过程中如果出错,没有重试逻辑,直接中断刷新流程。

修正方案

1. 全局维护连接池实例

将连接池和身份凭证提升到模块级作用域,确保刷新逻辑能正确访问和替换实例:

const mssql = require('mssql');
require('dotenv').config();
const { DefaultAzureCredential } = require("@azure/identity");

// 模块级变量存储当前连接池和身份凭证
let pool = null;
const credential = new DefaultAzureCredential({
  managedIdentityClientId: process.env.AZURE_DEV_MI_CLIENT_ID,
});

async function setupConnectionPool() {
  try {
    const accessToken = await credential.getToken("https://database.windows.net/.default");
    console.log('获取新令牌,过期时间:', new Date(accessToken.expiresOnTimestamp));

    // 安全关闭旧连接池
    if (pool) {
      try {
        await pool.close();
        console.log('旧连接池已关闭');
      } catch (closeErr) {
        console.error('关闭旧池失败:', closeErr);
      }
    }

    const config = {
      user: "",
      password: "",
      server: process.env.DB_SERVER,
      authentication: {
        type: "azure-active-directory-access-token",
        options: {
          token: accessToken.token,
        },
      },
      options: {
        database: process.env.DB_NAME,
        encrypt: true,
        port: 1433,
      },
    };

    // 创建并连接新池
    pool = new mssql.ConnectionPool(config);
    await pool.connect();
    console.log('新连接池已建立');

    // 提前10分钟刷新令牌,避免过期瞬间的连接失败
    const refreshOffset = 10 * 60 * 1000;
    const refreshTime = accessToken.expiresOnTimestamp - Date.now() - refreshOffset;
    setTimeout(refreshToken, refreshTime);

    // 添加连接池异常监听,主动触发刷新
    pool.on('error', (err) => {
      console.error('连接池异常,触发令牌刷新:', err);
      refreshToken();
    });

    return pool;
  } catch (error) {
    console.error("建立连接池失败:", error);
    // 刷新失败后5分钟重试
    setTimeout(refreshToken, 5 * 60 * 1000);
    throw error;
  }
}

// 独立的刷新函数
async function refreshToken() {
  console.log('开始刷新令牌...');
  try {
    await setupConnectionPool();
  } catch (err) {
    console.error('令牌刷新失败:', err);
  }
}

// 暴露获取当前有效池的方法,业务代码统一用这个获取
function getConnectionPool() {
  return pool;
}

module.exports = { setupConnectionPool, getConnectionPool }

2. 业务代码适配修改

server.js中不要直接存储setupConnectionPool的返回值,每次通过getConnectionPool()获取当前有效的连接池:

const { setupConnectionPool, getConnectionPool } = require('./db-connector');

// 初始化数据库连接
async function initDB() {
  await setupConnectionPool();
}

// 业务查询示例
async function fetchData() {
  const pool = getConnectionPool();
  if (!pool) throw new Error('数据库连接池未初始化');
  
  const result = await pool.request().query('SELECT * FROM your_table');
  return result.recordset;
}

3. 额外优化建议

  • 增加详细日志,记录令牌过期时间、刷新触发时间、连接池状态,方便排查问题。
  • 可以添加监控告警,当连接池连续刷新失败时,及时通知开发人员。

内容的提问来源于stack exchange,提问作者Mr. K

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 23:53:32