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

NestJS连接Azure PostgreSQL Flexible Server的Token自动刷新问题

NestJS + TypeORM 连接 Azure PostgreSQL 托管身份认证 Token 过期无法自动刷新问题

我们基于NestJS构建应用,使用TypeORM连接Azure PostgreSQL Flexible Server,采用托管身份无密码认证,通过Entra Token验证身份。应用启动后能正常连接数据库,但Token(有效期24小时)过期后,数据库连接无法自动刷新,导致应用崩溃。尝试用Azure Identity SDK在第23小时手动刷新Token,发现过了24小时仍未生成新Token。

当前核心代码如下:

DatabaseModule 代码

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ManagedIdentityCredential } from '@azure/identity';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      useFactory: async () => {
        const credential = new ManagedIdentityCredential(); 

        // 仅在模块初始化时获取一次Token
        const tokenResponse = await credential.getToken('https://ossrdbms-aad.database.windows.net/.default');

        return {
          type: 'postgres',
          host: '<YOUR_DATABASE_SERVER_NAME>.postgres.database.azure.com',
          port: 5432,
          username: '<YOUR_AAD_USER>@<YOUR_DATABASE_SERVER_NAME>',
          password: tokenResponse.token, // 固定使用初始化时的Token
          database: '<YOUR_DATABASE_NAME>',
          ssl: { rejectUnauthorized: false },
          entities: [/* 你的实体类 */],
          synchronize: true, // 生产环境请设为false
        };
      },
    }),
  ],
})
export class DatabaseModule {}

AppModule 代码

import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';

@Module({
  imports: [
    DatabaseModule,
    // 其他模块
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

问题根源

当前代码的useFactory仅在NestJS模块初始化时执行一次,Token也只获取一次并固定配置到TypeORM连接中。当Token过期后,连接池中的所有连接都会因身份验证失败而无法正常工作,且没有机制自动刷新Token并重建连接。

手动刷新Token无效的原因是:即使外部获取了新Token,TypeORM的连接池已经使用旧Token创建了连接,不会自动更新连接的认证信息。

解决方案

方案1:利用TypeORM连接池的beforeConnect钩子动态获取Token

TypeORM支持在创建每个连接前执行钩子函数,可在这里动态获取最新的Token,确保每个新连接都使用有效凭证:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ManagedIdentityCredential } from '@azure/identity';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      useFactory: () => {
        const credential = new ManagedIdentityCredential();
        return {
          type: 'postgres',
          host: '<YOUR_DATABASE_SERVER_NAME>.postgres.database.azure.com',
          port: 5432,
          username: '<YOUR_AAD_USER>@<YOUR_DATABASE_SERVER_NAME>',
          database: '<YOUR_DATABASE_NAME>',
          ssl: { rejectUnauthorized: false },
          entities: [/* 你的实体类 */],
          synchronize: true,
          // 每次创建连接前获取最新Token
          beforeConnect: async (connection) => {
            const tokenResponse = await credential.getToken('https://ossrdbms-aad.database.windows.net/.default');
            connection.options.password = tokenResponse.token;
          },
        };
      },
    }),
  ],
})
export class DatabaseModule {}

方案2:配置连接池自动回收过期连接

结合beforeConnect钩子,调整连接池参数,让闲置连接在Token过期前被回收,强制后续请求创建使用新Token的连接:

// 在TypeORM配置中添加连接池参数
{
  // ...其他配置
  pool: {
    max: 10, // 根据并发量设置最大连接数
    idleTimeoutMillis: 3600000, // 1小时闲置后回收连接,确保在Token过期前更换
    connectionTimeoutMillis: 20000, // 连接超时时间
  },
  beforeConnect: async (connection) => {
    const tokenResponse = await credential.getToken('https://ossrdbms-aad.database.windows.net/.default');
    connection.options.password = tokenResponse.token;
  },
}

方案3:自定义Token刷新服务监听连接错误

创建服务监听数据库连接错误,当检测到Token过期相关错误时,主动刷新Token并重启连接:

import { Injectable, OnModuleInit } from '@nestjs/common';
import { Connection } from 'typeorm';
import { ManagedIdentityCredential } from '@azure/identity';

@Injectable()
export class TokenRefreshService implements OnModuleInit {
  private credential = new ManagedIdentityCredential();

  constructor(private readonly connection: Connection) {}

  async onModuleInit() {
    // 监听连接错误
    this.connection.driver.connection.on('error', async (err) => {
      // 判断是否为Token过期类错误
      if (err.message.includes('FATAL:  password authentication failed for user') || err.message.includes('invalid token')) {
        await this.refreshTokenAndReconnect();
      }
    });

    // 定时刷新Token(提前1小时执行,避免过期)
    setInterval(async () => {
      await this.refreshTokenAndReconnect();
    }, 23 * 60 * 60 * 1000);
  }

  private async refreshTokenAndReconnect() {
    try {
      const tokenResponse = await this.credential.getToken('https://ossrdbms-aad.database.windows.net/.default');
      // 更新连接配置的密码
      this.connection.options.password = tokenResponse.token;
      // 关闭现有连接池并重新建立连接
      await this.connection.close();
      await this.connection.connect();
    } catch (err) {
      console.error('刷新Token并重建连接失败:', err);
    }
  }
}

在DatabaseModule中注册该服务:

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      // ...现有配置
    }),
  ],
  providers: [TokenRefreshService],
})
export class DatabaseModule {}

注意事项

  • 生产环境必须关闭synchronize: true,避免自动修改数据库结构导致数据风险。
  • 连接池参数需根据应用并发量和Token有效期合理调整,确保闲置连接在Token过期前被回收。
  • ManagedIdentityCredential会自动缓存未过期的Token,调用getToken时无需手动处理缓存逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 08:55:59