Wrong Thread.CurrentPrincipal in async WCF end-method

后端 未结 3 1622
你的背包
你的背包 2021-02-08 15:25

I have a WCF service which has its Thread.CurrentPrincipal set in the ServiceConfiguration.ClaimsAuthorizationManager.

When I implement the ser

相关标签:
3条回答
  • 2021-02-08 16:08

    Not really the answer to my question, but an alternate approach of implementing the WCF service (in .NET 4.5) that does not exhibit the same issues with Thread.CurrentPrincipal.

        public async Task<string> Method1()
        {
            // Audit log call (uses Thread.CurrentPrincipal)
    
            try
            {
                return await Task.Factory.StartNew(() => this.WorkerFunction());
            }
            finally 
            {
                // Audit log result (uses Thread.CurrentPrincipal)
            }
        }
    
        private string WorkerFunction()
        {
            // perform work
            return string.Empty;
        }
    
    0 讨论(0)
  • 2021-02-08 16:10

    The valid approach to this is to create an extension:

    public class SLOperationContext : IExtension<OperationContext>
    {
        private readonly IDictionary<string, object> items;
    
        private static ReaderWriterLockSlim _instanceLock = new ReaderWriterLockSlim();
    
        private SLOperationContext()
        {
            items = new Dictionary<string, object>();
        }
    
        public IDictionary<string, object> Items
        {
            get { return items; }
        }
    
        public static SLOperationContext Current
        {
            get
            {
                SLOperationContext context = OperationContext.Current.Extensions.Find<SLOperationContext>();
                if (context == null)
                {
                    _instanceLock.EnterWriteLock();
                    context = new SLOperationContext();
                    OperationContext.Current.Extensions.Add(context);
                    _instanceLock.ExitWriteLock();
                }
                return context;
            }
        }
    
        public void Attach(OperationContext owner) { }
        public void Detach(OperationContext owner) { }
    }
    

    Now this extension is used as a container for objects that you want to persist between thread switching as OperationContext.Current will remain the same.

    Now you can use this in BeginMethod1 to save current user:

    SLOperationContext.Current.Items["Principal"] = OperationContext.Current.ClaimsPrincipal;
    

    And then in EndMethod1 you can get the user by typing:

    ClaimsPrincipal principal = SLOperationContext.Current.Items["Principal"];
    

    EDIT (Another approach):

    public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
    {
        var task = Task.Factory.StartNew(this.WorkerFunction, state);
    
        var ec = ExecutionContext.Capture();
    
        return task.ContinueWith(res =>
            ExecutionContext.Run(ec, (_) => callback(task), null));
    }
    
    0 讨论(0)
  • 2021-02-08 16:20

    Starting with a summary of WCF extension points, you'll see the one that is expressly designed to solve your problem. It is called a CallContextInitializer. Take a look at this article which gives CallContextInitializer sample code.

    If you make an ICallContextInitializer extension, you will be given control over both the BeginXXX thread context AND the EndXXX thread context. You are saying that the ClaimsAuthorizationManager has correctly established the user principal in your BeginXXX(...) method. In that case, you then make for yourself a custom ICallContextInitializer which either assigns or records the CurrentPrincipal, depending on whether it is handling your BeginXXX() or your EndXXX(). Something like:

    public object BeforeInvoke(System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.IClientChannel channel, System.ServiceModel.Channels.Message request){
        object principal = null;
        if (request.Properties.TryGetValue("userPrincipal", out principal))
        {
            //If we got here, it means we're about to call the EndXXX(...) method.
            Thread.CurrentPrincipal = (IPrincipal)principal;
        }
        else
        {
            //If we got here, it means we're about to call the BeginXXX(...) method.
            request.Properties["userPrincipal"] = Thread.CurrentPrincipal;            
        }
        ...
     }
    

    To clarify further, consider two cases. Suppose you implemented both an ICallContextInitializer and an IParameterInspector. Suppose that these hooks are expected to execute with a synchronous WCF service and with an async WCF service (which is your special case).

    Below are the sequence of events and the explanation of what is happening:

    Synchronous Case

    ICallContextInitializer.BeforeInvoke();
    IParemeterInspector.BeforeCall();
    //...service executes...
    IParameterInspector.AfterCall();
    ICallContextInitializer.AfterInvoke();
    

    Nothing surprising in the above code. But now look below at what happens with asynchronous service operations...

    Asynchronous Case

    ICallContextInitializer.BeforeInvoke();  //TryGetValue() fails, so this records the UserPrincipal.
    IParameterInspector.BeforeCall();
    //...Your BeginXXX() routine now executes...
    ICallContextInitializer.AfterInvoke();
    
    //...Now your Task async code executes (or finishes executing)...
    
    ICallContextInitializercut.BeforeInvoke();  //TryGetValue succeeds, so this assigns the UserPrincipal.
    //...Your EndXXX() routine now executes...
    IParameterInspector.AfterCall();
    ICallContextInitializer.AfterInvoke();
    

    As you can see, the CallContextInitializer ensures you have opportunity to initialize values such as your CurrentPrincipal just before the EndXXX() routine runs. It therefore doesn't matter that the EndXXX() routine assuredly is executing on a different thread than did the BeginXXX() routine. And yes, the System.ServiceModel.Channels.Message object which is storing your user principal between Begin/End methods, is preserved and properly transmitted by WCF even though the thread changed.

    Overall, this approach allows your EndXXX(IAsyncresult) to execute with the correct IPrincipal, without having to explicitly re-establish the CurrentPrincipal in the EndXXX() routine. And as with any WCF behavior, you can decide if this applies to individual operations, all operations on a contract, or all operations on an endpoint.

    0 讨论(0)
提交回复
热议问题