You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何通过AAD/MFA凭据连接多Azure SQL数据库并避免重复认证

解决Azure SQL同实例多数据库MFA单次认证问题

你遇到的问题是因为每次创建新的SqlConnection时,交互式认证流程都会重新触发,没有复用之前获取的令牌。好在我们可以通过复用TokenCache来实现同一实例下多个数据库只需要一次浏览器认证,下面是具体的解决方案和代码修改:

核心思路

Azure SQL的交互式认证依赖于Azure AD的访问令牌,同一SQL实例下的所有数据库共享同一个资源URI(https://database.windows.net/),所以只要我们缓存已获取的有效令牌,后续连接就可以直接复用,无需重新弹出登录窗口。

修改后的代码实现

1. 改造认证提供者,添加全局令牌缓存

我们需要在ActiveDirectoryAuthProvider中添加一个静态的TokenCache实例,确保所有认证请求复用同一个缓存:

using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Data.SqlClient;
using System.Linq;

public class ActiveDirectoryAuthProvider : SqlAuthenticationProvider
{
    private readonly string _clientId = "MyClientID";
    // 全局静态TokenCache,所有认证请求共享
    private static readonly TokenCache _globalTokenCache = new TokenCache();

    public override async Task<SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters)
    {
        // 使用全局缓存初始化认证上下文
        var authContext = new AuthenticationContext(parameters.Authority, _globalTokenCache);
        authContext.CorrelationId = parameters.ConnectionId;

        // 先检查缓存中是否有未过期的有效令牌
        var validCachedToken = _globalTokenCache.ReadItems()
            .FirstOrDefault(t => t.Resource == parameters.Resource 
                                 && t.ExpiresOn > DateTimeOffset.Now);

        if (validCachedToken != null)
        {
            Console.WriteLine("复用缓存中的有效令牌");
            return new SqlAuthenticationToken(validCachedToken.AccessToken, validCachedToken.ExpiresOn);
        }

        AuthenticationResult result;
        switch (parameters.AuthenticationMethod)
        {
            case SqlAuthenticationMethod.ActiveDirectoryInteractive:
                Console.WriteLine("触发交互式认证获取新令牌");
                // 控制台应用使用urn:ietf:wg:oauth:2.0:oob作为重定向URI
                result = await authContext.AcquireTokenAsync(
                    parameters.Resource,
                    _clientId,
                    new Uri("urn:ietf:wg:oauth:2.0:oob"),
                    new PlatformParameters(PromptBehavior.Auto)); 
                    // PromptBehavior.Auto会自动判断是否需要弹出登录窗口,缓存有效时不会弹出
                break;
            case SqlAuthenticationMethod.ActiveDirectoryIntegrated:
                Console.WriteLine("使用集成认证获取新令牌");
                result = await authContext.AcquireTokenAsync(
                    parameters.Resource,
                    _clientId,
                    new UserCredential(GlobalSettings.CredentialsSettings.Username));
                break;
            default:
                throw new InvalidOperationException("不支持当前认证方法");
        }

        return new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn);
    }

    public override bool IsSupported(SqlAuthenticationMethod authenticationMethod)
    {
        return authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated ||
               authenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive;
    }
}

2. 确保认证提供者只初始化一次

Main方法中,只需要设置一次认证提供者,不要每次连接都重复设置:

static void Main(string[] args)
{
    // 仅初始化一次认证提供者
    var provider = new ActiveDirectoryAuthProvider();
    SqlAuthenticationProvider.SetProvider(
        SqlAuthenticationMethod.ActiveDirectoryInteractive,
        provider);
    SqlAuthenticationProvider.SetProvider(
        SqlAuthenticationMethod.ActiveDirectoryIntegrated,
        provider);

    // 后续可以多次调用GetConnection连接不同数据库
    using var db1Conn = GetConnection("Database1");
    // 执行数据库1的操作...

    using var db2Conn = GetConnection("Database2");
    // 执行数据库2的操作...
}

// 修改GetConnection方法,支持传入数据库名称
private SqlConnection GetConnection(string databaseName)
{
    var builder = new SqlConnectionStringBuilder();
    builder.DataSource = "MyServer.database.windows.net";
    builder.Encrypt = true;
    builder.TrustServerCertificate = false; // 建议关闭,使用Azure SQL的合法证书
    builder.PersistSecurityInfo = true;
    builder.Authentication = SqlAuthenticationMethod.ActiveDirectoryInteractive;
    builder.InitialCatalog = databaseName;
    
    var conn = new SqlConnection(builder.ToString());
    conn.Open();
    return conn;
}

关键说明

  • TokenCache复用:静态的_globalTokenCache会在应用生命周期内保存所有获取的令牌,同一资源URI的请求会优先使用缓存中的有效令牌。
  • PromptBehavior.Auto:这个参数会让Azure AD自动判断是否需要弹出登录窗口,如果缓存中有有效令牌则直接复用,不会弹出浏览器。
  • 同一实例资源共享:同一Azure SQL实例下的所有数据库都使用https://database.windows.net/作为资源URI,所以缓存的令牌完全可以跨数据库复用。

这样修改后,你第一次连接数据库时会弹出浏览器登录,后续连接同一实例下的其他数据库时,就会自动复用缓存的令牌,不再弹出登录窗口了。

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

火山引擎 最新活动