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

使用SQLProvider与Membership的ASP.NET应用切换Azure托管标识后数据库连接失败

Azure托管标识迁移至SQL Server时的登录失败问题

异常信息

异常类型: SqlException
异常消息: 用户''登录失败。
at System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, DbConnectionPool pool, String accessToken, Boolean applyTransientFaultHandling, SqlAuthenticationProviderManager sqlAuthProviderManager)
at System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection) at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource1 retry, DbConnectionOptions userOptions) at System.Data.SqlClient.SqlConnection.TryOpenInner(TaskCompletionSource1 retry)
at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
at System.Data.SqlClient.SqlConnection.Open()
at System.Web.DataAccess.SqlConnectionHolder.Open(HttpContext context, Boolean revertImpersonate)
at System.Web.DataAccess.SqlConnectionHelper.GetConnection(String connectionString, Boolean revertImpersonation)
at System.Web.Security.SqlRoleProvider.GetRolesForUser(String username)
at System.Web.Security.RolePrincipal.IsInRole(String role)
at System.Web.Configuration.AuthorizationRule.IsTheUserInAnyRole(StringCollection roles, IPrincipal principal)
at System.Web.Configuration.AuthorizationRule.IsUserAllowed(IPrincipal user, String verb)
at System.Web.Configuration.AuthorizationRuleCollection.IsUserAllowed(IPrincipal user, String verb)
at System.Web.Security.UrlAuthorizationModule.OnEnter(Object source, EventArgs eventArgs)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

应用web.config配置

<system.web>
  <roleManager enabled="true" cacheRolesInCookie="true" 
               defaultProvider="CustomizedRoleProvider" cookieTimeout="60">
    <providers>
      <clear />
      <add connectionStringName="ProjectConnection" 
           type="System.Web.Security.SqlRoleProvider, System.Web, Version=2.0.0.0, 
                 Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" applicationName="/" 
           name="CustomizedRoleProvider" />
    </providers>
  </roleManager>
  <membership defaultProvider="CustomizedProvider">
    <providers>
      <remove name="AspNetSqlMembershipProvider" />
      <add connectionStringName="ProjectConnection" enablePasswordRetrieval="false" 
           enablePasswordReset="true" requiresQuestionAndAnswer="false" applicationName="/" 
           requiresUniqueEmail="true" passwordFormat="Hashed" 
           maxInvalidPasswordAttempts="3" minRequiredNonalphanumericCharacters="1" 
           passwordAttemptWindow="3" minRequiredPasswordLength="8" 
           passwordStrengthRegularExpression="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$" 
           name="CustomizedProvider" type="System.Web.Security.SqlMembershipProvider, 
                 System.Web, Version=2.0.0.0,   Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </providers>
  </membership>
  <authentication mode="Forms">
    <forms slidingExpiration="true" loginUrl="Login/Login" defaultUrl="~/" 
           timeout="10800" requireSSL="false" protection="All" />
  </authentication>
  <authorization>
    <deny users="?" />
    <allow roles="Admin" />
  </authorization>
  <customErrors mode="Off" />
</system.web>
<connectionStrings>
  <add name="ProjectConnection" 
       connectionString="Server=tcp:xx.database.windows.net,1433;Database=xx;" />
</connectionStrings>

问题分析

空用户登录失败的核心原因是:原生SqlRoleProvider和SqlMembershipProvider不支持自动使用Azure托管标识认证,这类旧组件依赖传统SQL身份验证逻辑,不会主动获取托管标识的访问令牌并附加到数据库连接上;同时当前连接字符串未指定认证方式,导致客户端尝试匿名登录,最终失败。

解决方案

1. 更新连接字符串,指定托管标识认证

在ProjectConnection的连接字符串中添加Authentication=Active Directory Managed Identity参数,明确告知SQL客户端使用托管标识认证:

<add name="ProjectConnection" 
     connectionString="Server=tcp:xx.database.windows.net,1433;Database=xx;Authentication=Active Directory Managed Identity;" />

2. 升级SQL客户端库

旧版System.Data.SqlClient对Azure托管标识的支持有限,建议升级为Microsoft.Data.SqlClient(版本≥2.0),该库原生支持多种Azure AD认证方式,包括托管标识。

3. 自定义Membership/Role Provider

原生SqlMembershipProvider和SqlRoleProvider无法自动处理托管标识的令牌获取,需要自定义扩展类,在获取数据库连接时手动注入访问令牌:

自定义RoleProvider示例

using System.Web.Security;
using Microsoft.Data.SqlClient;
using Microsoft.Azure.Services.AppAuthentication;

public class ManagedIdentitySqlRoleProvider : SqlRoleProvider
{
    protected override SqlConnection GetConnection()
    {
        var connection = base.GetConnection() as SqlConnection;
        if (connection == null) return base.GetConnection();
        
        // 获取Azure SQL的访问令牌
        var tokenProvider = new AzureServiceTokenProvider();
        string accessToken = tokenProvider.GetAccessTokenAsync("https://database.windows.net/").Result;
        connection.AccessToken = accessToken;
        
        return connection;
    }
}

自定义MembershipProvider示例

using System.Web.Security;
using Microsoft.Data.SqlClient;
using Microsoft.Azure.Services.AppAuthentication;

public class ManagedIdentitySqlMembershipProvider : SqlMembershipProvider
{
    protected override SqlConnection GetConnection()
    {
        var connection = base.GetConnection() as SqlConnection;
        if (connection == null) return base.GetConnection();
        
        var tokenProvider = new AzureServiceTokenProvider();
        string accessToken = tokenProvider.GetAccessTokenAsync("https://database.windows.net/").Result;
        connection.AccessToken = accessToken;
        
        return connection;
    }
}

更新web.config中的Provider类型

将自定义Provider的类型配置到web.config中,替换原生的类型:

<!-- RoleProvider配置 -->
<add connectionStringName="ProjectConnection" 
     type="YourNamespace.ManagedIdentitySqlRoleProvider, YourAssemblyName" 
     applicationName="/" 
     name="CustomizedRoleProvider" />

<!-- MembershipProvider配置 -->
<add connectionStringName="ProjectConnection" enablePasswordRetrieval="false" 
     enablePasswordReset="true" requiresQuestionAndAnswer="false" applicationName="/" 
     requiresUniqueEmail="true" passwordFormat="Hashed" 
     maxInvalidPasswordAttempts="3" minRequiredNonalphanumericCharacters="1" 
     passwordAttemptWindow="3" minRequiredPasswordLength="8" 
     passwordStrengthRegularExpression="^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$" 
     name="CustomizedProvider" type="YourNamespace.ManagedIdentitySqlMembershipProvider, YourAssemblyName" />

4. 配置托管标识的数据库权限

在Azure SQL数据库中,为托管标识创建数据库用户,并赋予必要的权限(如读取/修改会员、角色表的权限):

-- 创建托管标识用户
CREATE USER [你的托管标识名称] FROM EXTERNAL PROVIDER;

-- 赋予db_owner权限(根据实际需求调整)
ALTER ROLE db_owner ADD MEMBER [你的托管标识名称];

5. 验证应用环境的托管标识配置

如果应用部署在Azure App Service:

  • 确认已开启系统分配或用户分配的托管标识;
  • 确认该托管标识已被添加到Azure SQL数据库的访问控制列表中,拥有访问权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 17:02:32