我有一个由IIS托管的Web应用程序。它配置有表单身份验证和匿名身份验证,并且启用了模拟。
应用程序池帐户是网络服务。匿名帐户是Costa。 Costa正在访问数据库。 NetworkService无法访问数据库。
问题在于请求线程(父线程)可以访问数据库,但是子线程不能。
要解决此问题。我将主线程的Windows标识对象发送到子线程,然后调用Impersonate()。模拟是指“使用模拟帐户分配当前线程Windows身份。
我的问题:这是一种好习惯吗?有风险吗?
\\Request thread code (Parent thread)
\\WindowsIdentity.GetCurrent() return Costa identity (impersonated)
requestFields.CurrentPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
ThreadPool.QueueUserWorkItem(LogRequest, requestFields);
-
\\ Sub thread code that works
RequestFields requestFields = (RequestFields)obj;
HttpRequest httpRequest = requestFields.Request;
var impersonationContext = ((WindowsIdentity)requestFields.CurrentPrincipal.Identity).Impersonate();
.
.
.
impersonationContext.Undo();
最佳答案
辅助线程没有自动从请求线程中模拟用户的原因是.NET线程池管理其线程的创建和重用。如果您是通过下面的示例代码自己创建线程的,那么我希望它自动继承安全上下文(包括模拟),但是您也不会重用线程,这会增加执行成本(有关详细信息,请参见Thread vs ThreadPool两者之间的差异以及使用线程池线程的好处)。
既然您知道IIS确实模拟了用户,并且根据http://blogs.msdn.com/b/tmarq/archive/2007/07/21/asp-net-thread-usage-on-iis-7-0-and-6-0.aspx,它使用线程池来处理其请求,所以我得出的结论是,只要您采取以下措施确保对线程池线程进行模拟就没有危险。即使在特殊情况下,假冒行为也有可能被撤销。如果模拟没有撤消,则存在将使用线程池的其他代码(您的库,其他库或.NET框架本身)分配给使用某种随机标识而不是应用程序池标识的线程的风险。
我不确定RequestFields类是什么(快速搜索似乎表明它不属于.NET框架),所以我不明白为什么有必要将WindowsIdentity包装在WindowsPrincipal中,因为您不需要使用除Identity之外的任何其他属性,它会强制您强制转换到另一个线程上。如果您拥有这个类并且可以更改它,我建议您将CurrentPrincipal属性更改为直接采用WindowsIdentity,以便可以在不需要不必要的WindowsPrincipal包装器的情况下进行传递。
我认为您可以将当前的WindowsIdentity传递给另一个线程池线程,并以执行方式调用模拟。但是,您绝对应该将impersonationContext包装在using块中,或者将Undo包装在try / finally块的最后一部分中,该try / finally块从对Impersonate的调用开始,以确保即使在发生异常或线程中断的情况下也可以撤消模拟。您还应该确保也处置由WindowsIdentity.GetCurrent()创建的WindowsIdentity的副本,以便确定性地关闭对身份背后的非托管用户令牌的所有引用(即,不通过垃圾回收器完成)。
用于创建新线程的示例代码:
Thread myThread = new Thread(LogRequest);
// the CLR will not wait for this thread to complete if all foreground threads
// have been terminated; this mimics the thread pool since all thread pool threads
//are designated as background threads
myThread.IsBackground = true;
myThread.Start(requestFields);
使用using块正确放置WindowsImpersonationContext和WindowsIdentity对象的示例代码(impersonationContext.Dispose将调用Undo):
using (var identity = (WindowsIdentity)requestFields.CurrentPrincipal.Identity)
using (var impersonationContext = identity.Impersonate())
{
.
.
.
}