使用SignalR与身份模拟时出现SQL Server用户登录失败问题
问题根因
SignalR 基于异步持久连接机制实现,Hub 方法的执行往往会调度到和初始请求不同的后台线程上,ASP.NET 默认不会将Web.config中配置的模拟上下文跨异步线程流转:
- 普通WebForm请求全程在请求初始线程同步执行,模拟上下文始终生效,因此数据库调用正常
- 后台执行线程默认继承IIS应用程序池的进程身份,当应用池使用内置账户(如Network Service、ApplicationPoolIdentity)运行时,访问SQL Server这类网络资源会使用当前计算机账户
MY_DOMAIN\MY_PC_NAME,因此抛出登录失败错误 Context.User.Identity是SignalR维护的HTTP层用户认证标识,和线程实际运行的Windows安全上下文(即SQL连接使用的身份)没有绑定关系,不能作为数据库调用身份的判断依据
解决方案
按推荐优先级从高到低排列:
方案1:直接修改应用程序池运行身份(零代码改动,最稳定)
业务要求所有数据库调用统一使用MY_DOMAIN\MY_USER身份时,直接将站点对应应用程序池的运行账号设置为该账号即可,从根源避免异步线程上下文丢失问题:
- 打开IIS管理器,定位到站点对应的应用程序池
- 右键选择「高级设置」,找到「进程模型」分类下的「标识」项
- 选择「自定义账户」,输入
MY_DOMAIN\MY_USER的账号密码,保存配置 - 回收应用程序池生效
配置完成后可以移除Web.config中的
<identity>模拟节点,不需要额外做模拟操作。注意需要给该账号授予站点目录的读取权限、IIS运行所需的最小权限即可。
方案2:在Hub方法内显式执行模拟(改动最小,适配现有逻辑)
不方便修改应用池配置时,可以在需要访问数据库的Hub方法中,显式从请求上下文中取出模拟身份并在执行数据库操作时生效,注意操作完成后必须撤销模拟:
using System; using System.Configuration; using System.Data.SqlClient; using System.Security.Principal; using System.Web; using Microsoft.AspNet.SignalR; public class SiteHub : Hub { public void QueryData() { // 从当前请求上下文拿到初始的模拟Windows身份 if (HttpContext.Current?.User?.Identity is WindowsIdentity winIdentity) { // 开启模拟上下文 using (winIdentity.Impersonate()) { // 该代码块内的所有SQL调用都会使用配置的模拟身份 var connStr = ConfigurationManager.ConnectionStrings["SiteDatabase"].ConnectionString; using (var conn = new SqlConnection(connStr)) { conn.Open(); // 执行数据库操作 } } // using结束后自动撤销模拟,回到原线程上下文 } } }
方案3:全局开启模拟上下文跨线程流动
不想修改业务代码时,可以通过配置让.NET 全局将模拟上下文流转到所有异步线程,适用于.NET Framework 4.5及以上版本。在Web.config的根节点下添加如下配置:
<configuration> <runtime> <legacyImpersonationPolicy enabled="false" /> <alwaysFlowImpersonationPolicy enabled="true" /> </runtime> </configuration>
该配置会对整个应用的所有异步逻辑生效,会带来一定的性能开销,部分老旧异步组件可能出现身份传递异常,非必要不推荐使用。
内容的提问来源于stack exchange,提问作者TechyGypo
相关产品推荐
相关产品推荐

