在模拟时调用异步WCF服务

时间:2014-02-07 12:53:15

标签: c# wcf asynchronous impersonation

我在服务器上运行WCF服务,该服务器配置为接受Kerberos身份验证。

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

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

在客户端,我有一个Controller(它是一个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);
        }
    }
}

只要我不使用等待,模仿就可以正常工作。

由于Task<>没有流出模拟身份,我想知道是否有可能更改Task的执行用户或做其他事情以使模拟工作在这个用例中。

我尝试了一个自定义awaiter(因为它可以在那个案例中使用Culture),但这根本不起作用(好吧它也没有冒充)。

3 个答案:

答案 0 :(得分:5)

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

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

aspnet.config中找到C:\Windows\Microsoft.Net\Framework64\v4.0.30319文件(可能这也适用于更高版本)并将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方式)

您可以使用此方法检查窗口标识是否流动:

System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed()

答案 1 :(得分:3)

我不同意你的问题。

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

如果您坚持使用真正的无线Task,那么您应该没有任何问题,因为您将保留在一个线程中。除非您的应用程序服务器具有非常有限的客户端数量和大量的CPU绑定操作,否则多线程不适合扩展,因为单个用户可以快速填写服务器的计划。

您应该使用Task.FromResultTaskCompletionSource.Task来确保您保持单线程。通过[ThreadLocal]属性可以解决您的问题。

TL:DR

请勿在服务器端使用Task.Run。使用Task.FromResult,因此您只有一个帖子。

编辑:响应

哪个帖子?在客户端,您仍将使用await。我从未说过不要使用await。我说过不要直接使用await Task.Run(除了UI线程)。我没有说你应该BLOCK一个线程。因为你的线程应该做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在本地解析的情况下,所有内容都会同步运行(速度更快,资源更少)。这里的要点是,您只对There is no thread样式的异步应用Task async模式。 Task.Run是async的并发风格,只应在需要使用另一个线程时使用(因为你是CPU绑定的,或者这个线程需要用于其他东西)。

答案 2 :(得分:0)

由于我负责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();
        }
    }
}

我不得不说这是一种解决方法 - 而不是解决方案 - 它改变了服务器端的接口(仅限非Async接口),但至少它正在运行。

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