使用托管标识与 Azure SQL 的 EF Core 连接
Posted
技术标签:
【中文标题】使用托管标识与 Azure SQL 的 EF Core 连接【英文标题】:EF Core Connection to Azure SQL with Managed Identity 【发布时间】:2019-01-14 18:32:22 【问题描述】:我正在使用 EF Core 连接到部署到 Azure 应用服务的 Azure SQL 数据库。我正在使用访问令牌(通过托管身份获得)连接到 Azure SQL 数据库。
这是我的做法:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
//code ignored for simplicity
services.AddDbContext<MyCustomDBContext>();
services.AddTransient<IDBAuthTokenService, AzureSqlAuthTokenService>();
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);
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;
这很好,我可以从数据库中获取数据。但我不确定这是否是正确的做法。
我的问题:
-
这是正确的方法还是会出现性能问题?
我需要担心令牌过期吗?我现在没有缓存令牌。
EF Core 有没有更好的方法来处理这个问题?
【问题讨论】:
你能给我一个编辑过的连接字符串吗,我不确定我是否使用了正确的 我正在使用server=tcp:my-server.database.windows.net,1433;Initial Catalog=my-database;Persist Security Info=False;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;
,但我仍然收到匿名登录失败错误
这是我的连接字符串Data Source=tcp:dbserver.database.windows.net,1433;Initial Catalog=dbname;
,类型是SQLAzure
。检查您的 appservice 帐户是否已添加到 Azure SQLServer。
【参考方案1】:
这是一种正确的方法吗?还是会出现性能问题?
这是正确的方法。为每个新的 DbContext 调用 OnConfiguring,因此假设您没有任何长期存在的 DbContext 实例,这是正确的模式。
我需要担心令牌过期吗?我现在没有缓存令牌。
AzureServiceTokenProvider
负责缓存。
EF Core 有没有更好的方法来处理这个问题?
设置 SqlConnection.AccessToken 是目前在 .NET Core 的 SqlClient 中使用 AAD Auth 的唯一方法。
【讨论】:
嗨,您能否指定 IDBAuthTokenService 使用了哪个 Nuget 包或命名空间。谢谢。 默认选择可能是nuget.org/packages/Microsoft.Azure.Services.AppAuthentication,但获取令牌有多种方式(包括REST)。 @PacodelaCruz 是的,请参阅docs.microsoft.com/en-us/azure/key-vault/… @DavidBrowne-MicrosoftIDBAuthTokenService
不能再在Microsoft.Azure.Services.AppAuthentication
中找到(如@buzzripper 所述)。我无法在其他任何地方找到它,因为对这个界面的唯一引用就是这个问题!
@DavidBrowne-Microsoft 这个解决方案调用.Result
会阻塞线程(即使你采用 REST 方式,你也会遇到同样的问题)。我知道它在 ctor 上,所以没有 async
来电。有没有更好的方法来设置 async
/await
时尚?【参考方案2】:
虽然该方法通常是正确的,因为除了必须编写自定义代码来设置连接的 AccessToken
之外别无他法,但您的实现中有几个问题可以通过使用DbConnectionInterceptor
我将在下面描述。这两个问题是:
-
您自己负责创建连接对象。但是你不处理它。在您的实施过程中,处置会很棘手,这就是您可能跳过它的原因。
您的代码被阻塞,因为您在等待访问令牌时使用
.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);
这是拦截器的实现:
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);
这是配置服务的方法:
services.AddSingleton(new DbContextFactoryOptions(connection_string));
services.AddSingleton(new AzureAuthenticationInterceptor(new AzureServiceTokenProvider()));
最后,这是在您的存储库中实例化 DbContext
对象的方法:
public async Task<IEnumerable<MyCustomEntity>> GetAll()
using var context = _notificationsDbContextFactory.Create(); // Injected in ctor
var dbos = await context.MyCustomEntity.ToListAsync();
return ... // something;
【讨论】:
1) DbContext 将负责关闭 SqlConnection。参见 docs.microsoft.com/en-us/dotnet/api/… 2) Sync 调用仅在缓存中没有令牌可用的情况下阻塞线程,并且阻塞线程不会花费任何 CPU 时间。 这里有一篇关于 DbConnectionInterceptor 方法和 Azure Identity + EF 的写得很好的文章:devblogs.microsoft.com/azure-sdk/… 它做了更多检查以查看是否需要令牌。【参考方案3】:对于那些仍然遇到同样问题的人,我已经通过使用DbInterceptor
解决了这个问题,因此我可以异步获取令牌而不会阻塞应用程序。我在 EF Core repo 上打开了一个问题,但我已经关闭了解决方案:
https://github.com/dotnet/efcore/issues/21043
希望对你有帮助。
【讨论】:
【参考方案4】:对于使用 .NET Framework for Managed Identity 的开发人员,以下代码可能有助于获取实体连接:
app.config:
<add key="ResourceId" value="https://database.windows.net/" />
<add key="Con" value="data source=tcp:sampledbserver.database.windows.net,1433;initial catalog=sampledb;MultipleActiveResultSets=True;Connect Timeout=30;" />
c#文件
using System;
using System.Configuration;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.SqlClient;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Azure.Services.AppAuthentication;
public static EntityConnection GetEntityConnectionString()
MetadataWorkspace workspace = new MetadataWorkspace(
new string[] "res://*/" ,
new Assembly[] Assembly.GetExecutingAssembly() );
SqlConnection sqlConnection = new SqlConnection(Con);
var result = (new AzureServiceTokenProvider()).GetAccessTokenAsync(ResourceId).Result;
sqlConnection.AccessToken = result ?? throw new InvalidOperationException("Failed to obtain the access token");
EntityConnection entityConnection = new EntityConnection(
workspace,
sqlConnection);
return entityConnection;
【讨论】:
【参考方案5】:赞成。
这是对 Romar 出色答案的附加答案。这对我们非常有用,并允许我们消除 ConnectionString 中的用户凭据。然而,这给我们留下了需要使用秘密检索访问令牌的问题,这是我们也不希望包含在 appsettings 文件中的敏感信息。因此,我们用一个问题换另一个问题。
网上还有其他帖子处理这个问题。因此,我发布了一个综合而全面的答案,该答案从 appsettings 文件中完全删除了敏感数据。注意:您需要将机密迁移到 KeyVault。在这种情况下,我们将其命名为AzureSqlSecret
。这是为了检索数据库用户的凭据。
调用AzureAuthenticationInterceptor
的Entities类构造函数如下:
public ProjectNameEntities() :
base(new DbContextOptionsBuilder<ProjectNameEntities>()
.UseSqlServer(ConfigurationManager.ConnectionStrings["ProjectNameEntities"].ConnectionString)
.AddInterceptors(new AzureAuthenticationInterceptor())
.Options)
AzureAuthenticationInterceptor:
#region NameSpaces
using Azure.Core;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System;
using System.Configuration;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
#endregion
namespace <ProjectName>.DataAccess.Helpers
public class AzureAuthenticationInterceptor : DbConnectionInterceptor
#region Constructor
public AzureAuthenticationInterceptor()
SecretClientOptions objSecretClientOptions;
string strAzureKeyVaultResourceIdentifier;
string strAzureKeyVault;
string strAzureKeyVaultUri;
strAzureKeyVaultResourceIdentifier = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:KeyVault"];
strAzureKeyVault = ConfigurationManager.AppSettings["Azure:KeyVaults:TaxPaymentSystem"];
strAzureKeyVaultUri = strAzureKeyVaultResourceIdentifier.Replace("0", strAzureKeyVault);
// Set the options on the SecretClient. These are default values that are recommended by Microsoft.
objSecretClientOptions = new SecretClientOptions()
Retry =
Delay= TimeSpan.FromSeconds(2),
MaxDelay = TimeSpan.FromSeconds(16),
MaxRetries = 5,
Mode = RetryMode.Exponential
;
this.SecretClient = new SecretClient(
vaultUri: new Uri(strAzureKeyVaultUri),
credential: new DefaultAzureCredential(),
objSecretClientOptions
);
this.KeyVaultSecret = this.SecretClient.GetSecret("AzureSqlSecret");
this.strKeyVaultSecret = this.KeyVaultSecret.Value;
this.strAzureResourceIdentifierAuthentication = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:Authentication"];
this.strAzureResourceIdentifierDatabase = ConfigurationManager.AppSettings["Azure:ResourceIdentifiers:DataBase"];
this.strClientId = ConfigurationManager.AppSettings["Azure:DatabaseUsername:ClientId"];
this.strTenantId = ConfigurationManager.AppSettings["Azure:TenantId"];
#endregion
#region Methods
public override async ValueTask<InterceptionResult> ConnectionOpeningAsync(
DbConnection objDbConnection,
ConnectionEventData objEventData,
InterceptionResult objReturn,
CancellationToken objCancellationToken = default)
_ILogger.Debug("Reached the Async Interceptor method");
if (objDbConnection is SqlConnection objSqlConnection)
objSqlConnection.AccessToken = GetAccessToken();
return objReturn;
public override InterceptionResult ConnectionOpening(
DbConnection objDbConnection,
ConnectionEventData objConnectionEventData,
InterceptionResult objReturn)
_ILogger.Debug("Reached the non-Async Interceptor method");
if (objDbConnection is SqlConnection objSqlConnection)
objSqlConnection.AccessToken = GetAccessToken();
return objReturn;
private string GetAccessToken()
AuthenticationContext objAuthenticationContext;
AuthenticationResult objAuthenticationResult;
ClientCredential objClientCredential;
objAuthenticationContext = new AuthenticationContext(string.Format("0/1"
, this.strAzureResourceIdentifierAuthentication
, this.strTenantId));
objClientCredential = new ClientCredential(this.strClientId, this.strKeyVaultSecret);
objAuthenticationResult = objAuthenticationContext.AcquireTokenAsync(this.strAzureResourceIdentifierDatabase, objClientCredential).Result;
return objAuthenticationResult.AccessToken;
#endregion
#region Properties
readonly <ProjectName>.Common.Logging.ILogger _ILogger = <ProjectName>.Common.Logging.LogWrapper.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private SecretClient SecretClient;
private KeyVaultSecret KeyVaultSecret;
private string strAzureResourceIdentifierDatabase;
private string strAzureResourceIdentifierAuthentication;
private string strKeyVaultSecret;
private string strClientId;
private string strTenantId;
#endregion
【讨论】:
【参考方案6】:在 Microsoft.Data.SqlClient 到来之后 - 新版本的实体框架核心连接器到 sql - 现在非常简单:
Install-Package Microsoft.Data.SqlClient -Version 4.0.1
将连接字符串添加到 Dotnet 核心应用程序,如下所示:
"Server=tcp:<server-name>.database.windows.net;Authentication=Active Directory Default; Database=<database-name>;"
然后使用它通过 Azure SQL 连接使用托管标识连接到 Azure SQL,如下所示:
using (SqlConnection _connection = new SqlConnection(sqlConnectionString))
_connection.Open();
// do some stuff with the sqlconnection to read or write record in SQL.
_connection.Close();
return true;
Refer here for detailed article
【讨论】:
以上是关于使用托管标识与 Azure SQL 的 EF Core 连接的主要内容,如果未能解决你的问题,请参考以下文章
具有托管标识(用户分配)的 Azure SQL 无法针对 AAD 使用
如果用户是用户分配托管标识,则确定 Azure SQL Server 中的用户名
为啥我的 Azure 应用服务无法使用托管标识连接到 Azure 存储帐户?