本文介绍了异步方法调用和模拟的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么模拟用户上下文仅在异步方法调用之前可用?我已经编写了一些代码(实际上基于Web API)来检查模拟的用户上下文的行为.

Why impersonation user context is available only until the async method call?I have written some code (actually based on Web API) to check the behavior of the impersonated user context.

async Task<string> Test()
{
    var context = ((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();
    await Task.Delay(1);
    var name = WindowsIdentity.GetCurrent().Name;
    context.Dispose();
    return name;
}

在这种情况下,令我惊讶的是,我将收到应用程序池用户的名称.在其下运行代码.这意味着我不再具有强制的用户上下文.如果将延迟更改为0,则将使呼叫同步:

To my surprise in this situation I will receive the name of the App pool user. under which the code is running. That means that I don't have the imprsonated user context anymore. If the delay is changed to 0, which makes the call synchronous:

async Task<string> Test()
{
    var context = ((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();
    await Task.Delay(0);
    var name = WindowsIdentity.GetCurrent().Name;
    context.Dispose();
    return name;
}

代码将返回当前模拟用户的名称.据我了解等待以及调试器显示的内容,在分配名称之前,不会调用context.Dispose().

Code will return the name of currently impersonated user.As far as I understand the await and what debugger shows as well, the context.Dispose() is not called until name is being assigned.

推荐答案

在ASP.NET中,AspNetSynchronizationContext不会像Thread.CurrentPrincipal那样自动由AspNetSynchronizationContext流动.每次ASP.NET输入新的池线程时,模拟上下文都会保存并设置此处到应用程序池用户的位置.当ASP.NET离开线程时,会在此处进行恢复.对于await延续,也会发生这种情况,这是延续回调调用(由AspNetSynchronizationContext.Post排队的那些)的一部分.

In ASP.NET, WindowsIdentity doesn't get automatically flowed by AspNetSynchronizationContext, unlike say Thread.CurrentPrincipal. Every time ASP.NET enters a new pool thread, the impersonation context gets saved and set here to that of the app pool user. When ASP.NET leaves the thread, it gets restored here. This happens for await continuations too, as a part of the continuation callback invocations (those queued by AspNetSynchronizationContext.Post).

因此,如果要跨ASP.NET中的多个线程保留身份,则需要手动进行传递.您可以为此使用本地或类成员变量.或者,您可以通过逻辑调用上下文 AsyncLocal<T> 或类似斯蒂芬·克莱里(Stephen Cleary)的AsyncLocal .

Thus, if you want to keep the identity across awaits spanning multiple threads in ASP.NET, you need to flow it manually. You can use a local or a class member variable for that. Or, you can flow it via logical call context, with .NET 4.6 AsyncLocal<T> or something like Stephen Cleary's AsyncLocal.

或者,如果您使用ConfigureAwait(false),则您的代码将按预期工作:

Alternatively, your code would work as expected if you used ConfigureAwait(false):

await Task.Delay(1).ConfigureAwait(false);

(请注意,尽管在这种情况下您会丢失HttpContext.Current.)

(Note though you'd lose HttpContext.Current in this case.)

上述方法之所以可行,是因为在没有同步上下文的情况下,WindowsIdentity确实会流过await .它以Thread.CurrentPrincipal相同的方式流动,即跨异步调用(但不在异步调用之外) ).我相信这是 SecurityContext 流,它本身是ExecutionContext的一部分,并显示了相同的写时复制行为.

The above would work because, in the absence of synchronization context, WindowsIdentity does gets flowed across await. It flows in pretty much the same way as Thread.CurrentPrincipal does, i.e., across and into async calls (but not outside those). I believe this is done as a part of SecurityContext flow, which itself is a part of ExecutionContext and shows the same copy-on-write behavior.

为支持此说法,我对控制台应用程序做了一些实验:

To support this statement, I did a little experiment with a console application:

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    class Program
    {
        static async Task TestAsync()
        {
            ShowIdentity();

            // substitute your actual test credentials
            using (ImpersonateIdentity(
                userName: "TestUser1", domain: "TestDomain", password: "TestPassword1"))
            {
                ShowIdentity();

                await Task.Run(() =>
                {
                    Thread.Sleep(100);

                    ShowIdentity();

                    ImpersonateIdentity(userName: "TestUser2", domain: "TestDomain", password: "TestPassword2");

                    ShowIdentity();
                }).ConfigureAwait(false);

                ShowIdentity();
            }

            ShowIdentity();
        }

        static WindowsImpersonationContext ImpersonateIdentity(string userName, string domain, string password)
        {
            var userToken = IntPtr.Zero;

            var success = NativeMethods.LogonUser(
              userName,
              domain,
              password,
              (int)NativeMethods.LogonType.LOGON32_LOGON_INTERACTIVE,
              (int)NativeMethods.LogonProvider.LOGON32_PROVIDER_DEFAULT,
              out userToken);

            if (!success)
            {
                throw new SecurityException("Logon user failed");
            }
            try
            {
                return WindowsIdentity.Impersonate(userToken);
            }
            finally
            {
                NativeMethods.CloseHandle(userToken);
            }
        }

        static void Main(string[] args)
        {
            TestAsync().Wait();
            Console.ReadLine();
        }

        static void ShowIdentity(
            [CallerMemberName] string callerName = "",
            [CallerLineNumber] int lineNumber = -1,
            [CallerFilePath] string filePath = "")
        {
            // format the output so I can double-click it in the Debuger output window
            Debug.WriteLine("{0}({1}): {2}", filePath, lineNumber,
                new { Environment.CurrentManagedThreadId, WindowsIdentity.GetCurrent().Name });
        }

        static class NativeMethods
        {
            public enum LogonType
            {
                LOGON32_LOGON_INTERACTIVE = 2,
                LOGON32_LOGON_NETWORK = 3,
                LOGON32_LOGON_BATCH = 4,
                LOGON32_LOGON_SERVICE = 5,
                LOGON32_LOGON_UNLOCK = 7,
                LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
                LOGON32_LOGON_NEW_CREDENTIALS = 9
            };

            public enum LogonProvider
            {
                LOGON32_PROVIDER_DEFAULT = 0,
                LOGON32_PROVIDER_WINNT35 = 1,
                LOGON32_PROVIDER_WINNT40 = 2,
                LOGON32_PROVIDER_WINNT50 = 3
            };

            public enum ImpersonationLevel
            {
                SecurityAnonymous = 0,
                SecurityIdentification = 1,
                SecurityImpersonation = 2,
                SecurityDelegation = 3
            }

            [DllImport("advapi32.dll", SetLastError = true)]
            public static extern bool LogonUser(
                    string lpszUsername,
                    string lpszDomain,
                    string lpszPassword,
                    int dwLogonType,
                    int dwLogonProvider,
                    out IntPtr phToken);

            [DllImport("kernel32.dll", SetLastError=true)]
            public static extern bool CloseHandle(IntPtr hObject);
        }
    }
}


已更新,正如@PawelForys在评论中建议的那样,自动传递模拟上下文的另一种方法是在全局aspnet.config文件中使用<alwaysFlowImpersonationPolicy enabled="true"/>(如果需要,还可以使用<legacyImpersonationPolicy enabled="false"/>,例如表示HttpWebRequest).


Updated, as @PawelForys suggests in the comments, another option to flow impersonation context automatically is to use <alwaysFlowImpersonationPolicy enabled="true"/> in the global aspnet.config file (and, if needed, <legacyImpersonationPolicy enabled="false"/> as well, e.g. for HttpWebRequest).

这篇关于异步方法调用和模拟的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-31 04:40