繁体   English   中英

TPL和假冒

[英]TPL and Impersonation

我正在使用Impersonator类(请参阅http://www.codeproject.com/KB/cs/zetaimpersonator.aspx )在运行时切换用户上下文。

与此同时,我现在正在将我的程序从单线程设计重构为多线程设计(使用TPL /主要是Task类型,即)。

由于这种模拟是在线程级别上使用本机API函数发生的事情,我想知道TPL与它兼容的程度。 如果我更改了任务中的用户上下文,那么如果任务完成并且线程返回到ThreadPool,那么该用户上下文是否仍然设置? 在此任务中启动的其他任务是否会隐式使用该上下文?

我试图自己找出单元测试,并从第一次单元测试中扣除:

  • 任务在线程内部启动,而模拟“神奇地”继承用户上下文。
  • 当原始任务/线程执行其Impersonation.Undo()时,不会撤消继承的模拟。

第二个单元测试显示,如果未明确撤消模拟,则用户上下文在返回到线程池的线程上“幸存”,并且其他后续任务现在可以在不同的用户上下文中随机运行,具体取决于它们被分配的线程至。

我的问题:有没有比通过本机API调用实现模拟更好的方法? 也许一个更专注于TPL并绑定到任务而不是线程? 如果有一个变化来减轻在随机环境中执行任务的风险,我很乐意这样做......

这些是我写的2个单元测试。 如果您想自己运行测试,则必须稍微修改代码以使用您自己的机制来接收用户凭据,并且肯定可以轻松删除log4net调用。

是的,我知道,Thread.Sleep()是不好的风格,我因为在那里懒惰而感到内疚... ;-)

  private string RetrieveIdentityUser()
  {
     var windowsIdentity = WindowsIdentity.GetCurrent();
     if (windowsIdentity != null)
     {
        return windowsIdentity.Name;
     }
     return null;
  }

  [TestMethod]
  [TestCategory("LocalTest")]
  public void ThreadIdentityInheritanceTest()
  {
     string user;
     string pw;
     Security.Decode(CredentialsIdentifier, out user, out pw);

     string userInMainThread = RetrieveIdentityUser();
     string userInTask1BeforeImpersonation = null;
     string userInTask1AfterImpersonation = null;
     string userInTask2 = null;
     string userInTask3 = null;
     string userInTask2AfterImpersonationUndo = null;

     var threadlock = new object();
     lock (threadlock)
     {
        new Task(
           () =>
           {
              userInTask1BeforeImpersonation = RetrieveIdentityUser();
              using (new Impersonator(user, Domain, pw))
              {
                 userInTask1AfterImpersonation = RetrieveIdentityUser();
                 lock (threadlock)
                 {
                    Monitor.Pulse(threadlock);
                 }
                 new Task(() =>
                 {
                    userInTask2 = RetrieveIdentityUser();
                    Thread.Sleep(200);
                    userInTask2AfterImpersonationUndo = RetrieveIdentityUser();
                 }).Start();
                 Thread.Sleep(100);
              }
           }).Start();

        Monitor.Wait(threadlock);
        RetrieveIdentityUser();
        new Task(() => { userInTask3 = RetrieveIdentityUser(); }).Start();
        Thread.Sleep(300);

        Assert.IsNotNull(userInMainThread);
        Assert.IsNotNull(userInTask1BeforeImpersonation);
        Assert.IsNotNull(userInTask1AfterImpersonation);
        Assert.IsNotNull(userInTask2);
        Assert.IsNotNull(userInTask3);

        // context in both threads equal before impersonation
        Assert.AreEqual(userInMainThread, userInTask1BeforeImpersonation);

        // context has changed in task1
        Assert.AreNotEqual(userInTask1BeforeImpersonation, userInTask1AfterImpersonation);

        // impersonation to the expected user
        Assert.AreEqual(Domain + "\\" + user, userInTask1AfterImpersonation);

        // impersonation is inherited
        Assert.AreEqual(userInTask1AfterImpersonation, userInTask2);

        // a newly started task from the main thread still shows original user context
        Assert.AreEqual(userInMainThread, userInTask3);

        // inherited impersonation is not revoked
        Assert.AreEqual(userInTask2, userInTask2AfterImpersonationUndo);
     }
  }

  [TestMethod]
  [TestCategory("LocalTest")]
  public void TaskImpersonationTest()
  {
     int tasksToRun = 100; // must be more than the minimum thread count in ThreadPool
     string userInMainThread = RetrieveIdentityUser();
     var countdownEvent = new CountdownEvent(tasksToRun);
     var exceptions = new List<Exception>();
     object threadLock = new object();
     string user;
     string pw;
     Security.Decode(CredentialsIdentifier, out user, out pw);
     for (int i = 0; i < tasksToRun; i++)
     {
        new Task(() =>
        {
           try
           {
              try
              {
                 Logger.DebugFormat("Executing task {0} on thread {1}...", Task.CurrentId, Thread.CurrentThread.GetHashCode());
                 Assert.AreEqual(userInMainThread, RetrieveIdentityUser());
                 //explicitly not disposing impersonator / reverting impersonation
                 //to see if a thread reused by TPL has its user context reset
                 // ReSharper disable once UnusedVariable
                 var impersonator = new Impersonator(user, Domain, pw);
                 Assert.AreEqual(Domain + "\\" + user, RetrieveIdentityUser());
              }
              catch (Exception e)
              {
                 lock (threadLock)
                 {
                    var newException = new Exception(string.Format("Task {0} on Thread {1}: {2}", Task.CurrentId, Thread.CurrentThread.GetHashCode(), e.Message));
                    exceptions.Add(newException);
                    Logger.Error(newException);
                 }
              }
           }
           finally
           {
              countdownEvent.Signal();
           }
        }).Start();
     }
     if (!countdownEvent.Wait(TimeSpan.FromSeconds(5)))
     {
        throw new TimeoutException();
     }
     Assert.IsTrue(exceptions.Any());
     Assert.AreEqual(typeof(AssertFailedException), exceptions.First().InnerException.GetType());
  }

}

WindowsIdentity (通过模拟更改)存储在SecurityContext 您可以通过各种方式确定此模拟如何“流动”。 既然你提到过,你正在使用p / invoke,请注意SecurityContext文档中的警告:

公共语言运行库(CLR)知道仅使用托管代码执行的模拟操作,而不是在托管代码之外执行的模拟,例如通过平台调用...

但是,我并不完全确定模仿是真的从一个任务继承到另一个任务,或者您观察的行为是否是由于任务内联。 在某些情况下,新任务可能在同一个线程池线程上同步执行。 你可以在这里找到一个很好的讨论。

尽管如此,如果您想确保某些任务始终在模拟下运行,而其他任务却没有,我可以建议您查看自定义任务计划程序。 MSDN上有关于如何编写自己的文档,包括可用作起点的几种常见调度程序的代码示例

由于模拟是每线程设置,因此您可以拥有自己的任务调度程序,该任务调度程序在执行任务时保留在模拟下运行的一个线程(或几个线程)。 当你有许多小工作单元时,这也可以减少你进出模拟的次数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM