Parallel.ForEach()更改模拟上下文

时间:2014-09-26 17:29:56

标签: c# .net task-parallel-library impersonation parallel.foreach

今天我们将新创建的ASP.NET应用程序部署到服务器,很快我们就意识到存在一个与安全相关的奇怪问题导致应用程序崩溃。这是一个内部应用程序,我们使用Impersonation来管理用户访问资源的方式。但是,该应用程序会引发拒绝访问"访问被拒绝"当用户试图访问他们完全控制的文件夹时出现异常。

该异常实际上是一个AggregateException,并且被抛出一个方法,该方法使用Parallel.ForEach枚举列表并在正文内部,它尝试访问该文件夹,但此时模拟上下文被更改,工作线程作为应用程序池的标识运行,该标识不能访问该文件夹,因此也是例外。

为了确认这一点,我查看了Parallel.ForEach

正文内部和内部的进程标识
string before = WindowsIdentity.GetCurrent().Name;
Debug.WriteLine("Before Loop: {0}", before);

Parallel.ForEach(myList, currentItem =>
{
    string inside = WindowsIdentity.GetCurrent().Name;
    Debug.WriteLine("Inside Loop: {0} (Worker Thread {1})", inside, Thread.CurrentThread.ManagedThreadId);
});

当我运行应用程序时,这就是打印出来的内容:

Before Loop: MyDomain\ImpersonatedUser

Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 8)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 6)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)
Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 9)
Inside Loop: NT AUTHORITY\SYSTEM (Worker Thread 10)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 6)
Inside Loop: MyDomain\ImpersonatedUser (Worker Thread 7)

正如您所看到的,一些线程作为模拟身份运行,一些线程作为应用程序池运行(在本例中为LocalSystem)并且似乎不是一种模式。 Call Stack窗口中的前一帧也转到非托管kernel32.dll,这使我认为CLR在将其委托给操作系统之前不会验证上下文。

知道为什么会这样吗?这是一个已知的问题/错误吗?

3 个答案:

答案 0 :(得分:4)

Task类不同,Parallel似乎无法捕获您当前正在运行的ExecutionContext(后者又会捕获SecurityContext其中包含WindowsIdentity)。它使用当前线程中的一个可用。

您必须明确捕获所需的上下文:

IntPtr token = WindowsIdentity.GetCurrent().Token;

Parallel.ForEach(myList, currentItem =>
{
   using (WindowsIdentity.Impersonate(token))
   {
      string inside = WindowsIdentity.GetCurrent().Name;
      Debug.WriteLine("Inside Loop: {0} (Worker Thread {1})", inside, Thread.CurrentThread.ManagedThreadId);
   }
});

答案 1 :(得分:0)

这是一个 C# 扩展方法,可以让这更容易一些...

public static ParallelLoopResult ParallelForEach<TSource>(this IEnumerable<TSource> source, IPrincipal principal, ParallelOptions parallelOptions, Action<TSource> body)
        {
            return Parallel.ForEach(source, parallelOptions, (source) =>
            {
                if (!CurrentUser.Instance.IsAuthenticated)
                    Thread.CurrentPrincipal = principal;
                body(source);
            });
        }

这是你如何称呼它的:

puppies.ParallelForEach(
                    CurrentUser.Instance.Principal,
                    new ParallelOptions { MaxDegreeOfParallelism = 8 },
                    (puppy) => PetAnimal(puppy)
                );

答案 2 :(得分:-1)

Windows中的整个模拟概念是每线程概念。 您会看到,当创建新线程时,它会继承进程&#39;特权令牌。 但是线程进程不同,也有一个模拟令牌。当您调用WindowsIdentity.Impersonate(令牌)时,您在调用线程上设置模拟令牌。

同样,当您调用Impersonate时,您将线程的主要令牌指针设置为指向模拟令牌而不是进程&#39;主令牌,这是默认值。

WinAPI中的更多理解和知识会让您知道您的服务中发生的事情是预期的行为。