【问题标题】:Calling an async WCF Service while being impersonated在被模拟时调用异步 WCF 服务
【发布时间】:2014-03-04 21:34:41
【问题描述】:

我有一个在服务器上运行的 WCF 服务,它被配置为接受 Kerberos 身份验证。

Kerberos 工作正常,因此 WCF 服务知道哪个用户正在连接到他。 该服务以异步方法的形式提供一切。像这里这样(只是一个清晰的例子)。

public ExampleService : IExampleService {
    public Task<string> GetUsernameAsync() {
       return await Task.Run(() => System.Threading.Thread.CurrentPrincipal.Name);
    }
}

在客户端我有一个控制器(它是一个 MVC 页面,但没关系),它异步调用方法。

public ExampleController {
    public async Task<ActionResult> Index() {
        using(var serviceClient = ServiceFactory.GetServiceClient())
        using(Security.Impersonation.Impersonate())
        {
            var data = await serviceClient.GetUsernameAsync();
            return View(data);
        }
    }
}

只要我不使用 await,模拟就可以正常工作。

由于Task&lt;&gt; 不流动模拟身份,我想知道是否有可能更改Task 的执行用户或做任何其他事情以使模拟在此使用中工作-案子。

我尝试了一个自定义等待器(因为它可以在这种情况下使用 Culture 完成),但这根本不起作用(它只是不能模拟)。

【问题讨论】:

    标签: c# wcf asynchronous impersonation


    【解决方案1】:

    好的 - 经过一些更深入的研究,我终于找到了如何在异步任务中流动模拟窗口身份的解决方案。

    该解决方案是机器范围的,将为所有(在本例中)64 位 ASP.NET 4.5 应用程序设置。

    C:\Windows\Microsoft.Net\Framework64\v4.0.30319 中找到aspnet.config 文件(可能这也适用于以后的版本)并将legacyImpersonationPolicy 的值更改为false

    <legacyImpersonationPolicy enabled="false"/>
    

    确保重新启动 IIS(或重新启动计算机)。
    只要您使用 托管 方法进行模拟,这将使模拟流动。在我的情况下,我模仿与此类似,效果很好:

    class Impersonation : IDisposable
        {
            public static Impersonation Impersonate()
            {
                return new Impersonation();
            }
    
            private WindowsImpersonationContext ImpersonationContext { get; set; }
    
            private Impersonation()
            {
                var currentIdentity = System.Threading.Thread.CurrentPrincipal.Identity as WindowsIdentity;
                if (currentIdentity != null && currentIdentity.IsAuthenticated)
                {
                    ImpersonationContext = currentIdentity.Impersonate();
                    return;
                }
    
                throw new SecurityException("Could not impersonate user identity");
            }
    
            public void Dispose()
            {
                if(ImpersonationContext != null)
                    ImpersonationContext.Dispose();
            }
        }
    }
    

    aspnet.config 设置(顺便说一句。在 web.config 文件中设置它不起作用)在这里解释:http://msdn.microsoft.com/en-us/library/ms229296(v=vs.110).aspx(它基本上是说,如果这是真的,我们用 .NET 1.1 的方式来做)

    您可以使用此方法检查windows身份是否流动:

    System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed()
    

    【讨论】:

      【解决方案2】:

      我不同意你的问题。

      问题不在于您的await。但是你的Task.Run。你的 ASP.Net 代码上真的不应该有await Task.Run。它的效果是不必要的线程切换。由于您在 ASP.Net 上没有 STA 线程,因此没有必要这样做,它只会减慢您的代码速度。

      如果您坚持真正的无线程Tasks,您应该不会有任何问题,因为您将停留在单个线程中。除非您的应用程序服务器的客户端数量非常有限并且需要大量 CPU 操作,否则多线程不利于扩展,因为单个用户可以快速填满服务器的日程安排。

      您确实应该使用Task.FromResultTaskCompletionSource.Task 来确保您保持单线程。顺便说一句,这将解决您的 [ThreadLocal] 属性问题。

      TL:DR

      不要在服务器端使用Task.Run。使用Task.FromResult,这样你就只有一个线程了。

      编辑:响应

      哪个线程?在客户端,您仍将使用await。我从来没有说过不要使用await。我说过不要直接将awaitTask.Run 一起使用(UI 线程除外)。我没有说你应该阻止一个线程。因为你的线程应该做 WORK 来产生你传递给Task.FromResult 的结果。 BLOCKING 意味着您的线程什么都不做,同时消耗资源(即内存)。哎呀,甚至没有必要

      服务器端应该使用这种模式:

      public ExampleService : IExampleService 
      {
          public Task<string> GetUsernameAsync() 
          {
             var name = System.Threading.Thread.CurrentPrincipal.Name;
             return Task.FromResult(name);
          }
      }
      

      客户应该保留

      public ExampleController 
      {
          public async Task<ActionResult> Index() 
          {
              using(var serviceClient = ServiceFactory.GetServiceClient())
              using(Security.Impersonation.Impersonate())
              {
                  var data = await serviceClient.GetUsernameAsync();
                  return View(data);
              }
          }
      }
      

      如果您的 ServiceClient 在本地解析,一切都会同步运行(更快且资源更少)。这里的重点是,您仅将 Task async 模式应用于 There is no thread 异步样式。 Task.Run 是异步的并发风格,只应在您需要使用另一个线程时使用(因为您受 CPU 限制,或者此线程需要用于其他用途)。

      【讨论】:

      • 这意味着我宁愿阻塞线程长达 5 秒而不是使用异步和等待?
      • 不。永远不会发生阻塞。但同时,不应发生不必要的线程切换。你应该阅读Stephen Cleary on how to use async and Task
      • 啊,现在我明白你的意思了,但是服务器端的 Task.Run 只是一个速记解释。但你可能在这一点上是对的。但我的问题与服务器端无关 - 在客户端 await 上的模拟失败(或没有流动)。
      【解决方案3】:

      由于我在这里负责 WCF 接口,因此这是一种可行的解决方案(但我不喜欢,因为它或多或少是代码重复):

      [ServiceContract]
      interface IExampleService {
          [OperationContract]
          string GetUsername();
      }
      
      interface IExampleServiceAsync {
          Task<string> GetUserNameAsync();
      }
      
      class ExampleService : IExampleService {
          public string GetUsername() {
              return System.Threading.Thread.CurrentPrincipal.Name;
          }
      }
      
      class ExpampleServiceClient : ServiceClient<IExampleService>, IExampleServiceAsync {
          public Task<string> GetUsernameAsync() {
              return Task.Run(() => GetUsername());
          }
      
          private string GetUsername() {
              using(Security.Impersonation.Impersonate())
              {
                  return base.Proxy.GetUsername();
              }
          }
      }
      

      我不得不说这是一种解决方法——而不是解决方案——它改变了服务器端的接口(仅限于非异步接口),但至少它是有效的。

      此解决方案的一个优点 - 您可以在 ExampleServiceClient 之上将模拟实现为行为模式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-05-04
        • 2012-10-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-21
        • 2018-09-07
        相关资源
        最近更新 更多