EF Core连接到具有托管身份的Azure SQL

use*_*744 17 azure-active-directory entity-framework-core azure-sql-database ef-core-2.2

我正在使用EF Core连接到已部署到Azure App Services的Azure SQL数据库。我正在使用访问令牌(通过托管身份获取)连接到Azure SQL数据库。

这是我的做法:

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    //code ignored for simplicity
    services.AddDbContext<MyCustomDBContext>();

    services.AddTransient<IDBAuthTokenService, AzureSqlAuthTokenService>();
}
Run Code Online (Sandbox Code Playgroud)

MyCustomDBContext.cs

public partial class MyCustomDBContext : DbContext
{
    public IConfiguration Configuration { get; }
    public IDBAuthTokenService authTokenService { get; set; }

    public CortexContext(IConfiguration configuration, IDBAuthTokenService tokenService, DbContextOptions<MyCustomDBContext> options)
        : base(options)
    {
        Configuration = configuration;
        authTokenService = tokenService;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        SqlConnection connection = new SqlConnection();
        connection.ConnectionString = Configuration.GetConnectionString("defaultConnection");
        connection.AccessToken = authTokenService.GetToken().Result;

        optionsBuilder.UseSqlServer(connection);
    }
}
Run Code Online (Sandbox Code Playgroud)

AzureSqlAuthTokenService.cs

public class AzureSqlAuthTokenService : IDBAuthTokenService
{
    public async Task<string> GetToken()
    {
        AzureServiceTokenProvider provider = new AzureServiceTokenProvider();
        var token = await provider.GetAccessTokenAsync("https://database.windows.net/");

        return token;
    }
}
Run Code Online (Sandbox Code Playgroud)

这工作正常,我可以从数据库中获取数据。但是我不确定这是否是正确的方法。

我的问题:

  1. 这是正确的方法还是会出现性能问题?
  2. 我需要担心令牌到期吗?截至目前,我还没有缓存令牌。
  3. EF Core是否有更好的方法来解决此问题?

rom*_*mar 16

虽然该方法通常是正确的,因为除了必须编写设置AccessToken连接的自定义代码之外别无他法,但您的实现中有几个问题可以通过使用 a 来避免,DbConnectionInterceptor我将在下面描述。这两个问题是:

  1. 您自己负责创建连接对象。但你不处理它。在您的实现中处理会很棘手,这就是您可能跳过它的原因。
  2. 您的代码正在阻塞,因为您.Result在等待访问令牌时使用阻塞。

更好的替代方法是使用 EF Core 支持的拦截器。你将从这样的开始DbContext

public class MyCustomDbContextFactory : IMyCustomDbContextFactory
{
    private readonly string _connectionString;
    private readonly AzureAuthenticationInterceptor _azureAuthenticationInterceptor;
    public MyCustomDbContextFactory(DbContextFactoryOptions options, AzureAuthenticationInterceptor azureAuthenticationInterceptor)
    {
        _connectionString = options.ConnectionString;
        _azureAuthenticationInterceptor = azureAuthenticationInterceptor;
    }
    public MyCustomDbContext Create()
    {
        var optionsBuilder = new DbContextOptionsBuilder<MyCustomDbContext>();
        optionsBuilder
            .UseSqlServer(_connectionString)
            .AddInterceptors(_azureAuthenticationInterceptor);
        return new MyCustomDbContext(optionsBuilder.Options);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是拦截器的实现:

public class AzureAuthenticationInterceptor : DbConnectionInterceptor
{
    private const string AzureDatabaseResourceIdentifier = "https://database.windows.net";
    private readonly AzureServiceTokenProvider _azureServiceTokenProvider;
    public AzureAuthenticationInterceptor(AzureServiceTokenProvider azureServiceTokenProvider) : base()
    {
        _azureServiceTokenProvider = azureServiceTokenProvider;
    }
    public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
    {
        if (connection is SqlConnection sqlConnection)
        {
            sqlConnection.AccessToken = await GetAccessToken();
        }
        return result;
    }
    public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
    {
        if (connection is SqlConnection sqlConnection)
        {
            sqlConnection.AccessToken = GetAccessToken().Result;
        }
        return result;
    }
    private Task<string> GetAccessToken() => _azureServiceTokenProvider.GetAccessTokenAsync(AzureDatabaseResourceIdentifier);
}
Run Code Online (Sandbox Code Playgroud)

这是配置服务的方法:

services.AddSingleton(new DbContextFactoryOptions(connection_string));
services.AddSingleton(new AzureAuthenticationInterceptor(new AzureServiceTokenProvider()));
Run Code Online (Sandbox Code Playgroud)

最后,这是DbContext在存储库中实例化对象的方法:

public async Task<IEnumerable<MyCustomEntity>> GetAll()
{
using var context = _notificationsDbContextFactory.Create();  // Injected in ctor
var dbos = await context.MyCustomEntity.ToListAsync();
return ... // something;
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*oft 10

这是正确的方法还是会出现性能问题?

那是正确的方法。每个新的DbContext都会调用OnConfiguring,因此,假设您没有任何长期存在的DbContext实例,那么这是正确的模式。

我需要担心令牌到期吗?截至目前,我还没有缓存令牌。

AzureServiceTokenProvider 负责缓存。

EF Core是否有更好的方法来解决此问题?

当前,设置SqlConnection.AccessToken是在SqlClient for .NET Core中使用AAD Auth的唯一方法。

  • @PacodelaCruz 是的,请参阅 https://docs.microsoft.com/en-us/azure/key-vault/service-to-service-authentication (2认同)