Calling an async WCF Service while being impersona

2019-06-20 08:54发布

I have a WCF Service running on a Server, which is configured to accept Kerberos authentication.

Kerberos works fine and the WCF Service therefore knows, which user is connecting to him. The Service offers everything as Async Methods. Like this here (just an example for clearity).

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

On the Client side I have an Controller (it's an MVC-page, but that does not matter), which calls the methods asyncronously.

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

The impersonation works fine, as long as I do not use await.

Since Task<> does not flow the impersonated identity, I'd like to know if there is some possibility, to change the executing user of the Task or to do anything else to make the impersonation work in this use-case.

I tried a custom awaiter (as it can be done with Culture in that Case), but that does not work at all (Well it just does not impersonate as well).

3条回答
我欲成王,谁敢阻挡
2楼-- · 2019-06-20 09:03

Since I am in charge of the WCF interfaces here is one solution, which works (but which I do not like, since it is more or less code duplication):

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

I have to say this is a workaround - not a solution - and it changes the Interfaces on Server-Side (to non-Async interfaces only), but at least it is working.

One plus for this solution - you can implement the impersonation as a behavior pattern on top of the ExampleServiceClient.

查看更多
欢心
3楼-- · 2019-06-20 09:10

Okay - after some more in depth research I finally found the solution how to flow impersonated windows identities across asynchronous tasks.

The solution is machine-wide and will be set for all (in this case) 64bit ASP.NET 4.5 applications.

Find the aspnet.config file in C:\Windows\Microsoft.Net\Framework64\v4.0.30319 (probably this will apply for later versions, too) and change the value of legacyImpersonationPolicy to false

<legacyImpersonationPolicy enabled="false"/>

Make sure to restart IIS (or reboot the machine).
This will then make Impersonation flowing, as long as you use managed methods for the impersonation. In my case I impersonate similar to this, which works fine:

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

The aspnet.config setting (btw. it did not work to set it in the web.config file) is explained here: http://msdn.microsoft.com/en-us/library/ms229296(v=vs.110).aspx (it basically says, if this is true, we do it the .NET 1.1 way)

You can check, if the windows identity is flowed or not by using this method:

System.Security.SecurityContext.IsWindowsIdentityFlowSuppressed()
查看更多
Rolldiameter
4楼-- · 2019-06-20 09:25

I disagree with your QUESTION.

The problem isn't your await. But your Task.Run. There should really not be a await Task.Run on you ASP.Net code. The effect of it is an unnecessary thread switch. Since you don't have STA threads on ASP.Net, there is no need for this and it just slows down your code.

If you stick to real threadless Tasks you shouldn't have any problems, as you will stay in a single thread. Unless your application server has a very limited number of clients and a huge amount of CPU bound operations, multi-threading is bad for scaling, as a single user can quickly fill up the schedule of your server.

You should really be using Task.FromResult, or TaskCompletionSource.Task to ensure that you remain single-thread. Which co-incidentally will fix your problem with [ThreadLocal] properties.

TL:DR

Don't use Task.Run on your server-side. Use Task.FromResult so you only have one thread.

EDIT:Response

Which thread? On the client side you are still going to use await. I never said don't use await. I said DON'T ever use await directly with Task.Run (except on the UI thread). I didn't say you should BLOCK a thread. As your thread should be doing WORK to produce the result that you pass into the Task.FromResult. BLOCKING means that you thread does nothing, whilst consuming resources (namely memory). Heck, there isn't even a need to

Server side should use this pattern:

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

client should remain

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

and in the case where your ServiceClient resolves locally, everything runs synchronously (faster and with less resources). The point here is that you are only applying the Task async pattern for the There is no thread style of async. Task.Run is the concurrency style of async, and should only be used when you need to use another thread (either because you are CPU bound, or THIS thread NEEDS to be used for something else).

查看更多
登录 后发表回答