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

C# Parallel.ForEach()更改模拟上下文,c#,.net,task-parallel-library,impersonation,parallel.foreach,C#,.net,Task Parallel Library,Impersonation,Parallel.foreach,今天,我们将新创建的ASP.NET应用程序部署到服务器上,很快我们意识到存在一个奇怪的安全相关问题,导致应用程序崩溃。这是一个内部应用程序,我们使用模拟来管理用户访问资源的方式。但是,当用户试图访问其完全控制的文件夹时,应用程序会抛出“拒绝访问”异常 该异常实际上是一个AggregateException异常,并在使用Parallel的方法中引发。ForEach要在列表上和正文中枚举,它会尝试访问文件夹,但此时模拟上下文会发生更改,工作线程作为应用程序池的标识运行,无法访问该文件夹,因此出现异常

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

该异常实际上是一个
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
),似乎没有模式。
调用堆栈
窗口中的前一帧也指向非托管的
kernel32.dll
,这使我认为CLR在将上下文委托给操作系统之前没有验证上下文


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

任务
类不同,
并行
似乎没有捕获您当前运行的
执行上下文
(它依次捕获保存
窗口标识的
安全上下文
)。它使用当前线程中可用的线程

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

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);
   }
});

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

另外,在调用模拟时,您将线程的主令牌指针设置为指向模拟令牌,而不是进程的主令牌,这是默认值

对WinAPI多一点理解和了解会让您知道,您的服务中发生的是预期的行为。

这里有一个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);
            });
        }

但为什么它看起来确实不是一种模式呢?我不太确定。我以前见过关于这种方式的问题。似乎是应该实现的功能,因为这是预期的行为。是的,我正在使用
WindowsIdentity.GetCurrent().Impersonate()
作为解决方法,但我仍然不理解线程之间为什么存在差异。如果未捕获
SecurityContext
,为什么有些线程仍然作为模拟身份运行?我觉得还有更多。是的,我想知道的是相同的:要么总是捕获上下文,要么不总是捕获上下文。但实际上有时候是这样的。我认为
Parallel
也使用当前正在执行的线程作为其线程池的一部分。这就是为什么我们有时会看到正确的身份。少一点恶作剧会让世界变得更好,这应该是我们期望的行为。
puppies.ParallelForEach(
                    CurrentUser.Instance.Principal,
                    new ParallelOptions { MaxDegreeOfParallelism = 8 },
                    (puppy) => PetAnimal(puppy)
                );