【问题标题】:Async method call and impersonation异步方法调用和模拟
【发布时间】:2015-10-09 22:31:22
【问题描述】:

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

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,则调用同步:

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() 。

【问题讨论】:

  • 您已经模拟了一些随机线程池线程。在其上运行的下一个请求可能会受此影响。超级危险。
  • @usr,事实证明,除非你冒充UnsafeQueueUserWorkItem 之类的内部人员,否则它并没有那么危险。否则,身份会正确传播和恢复,它不会挂在池线程上。请参阅this little experiment,尤其是GoThruThreads。在 ASP.NET 中更加安全,请查看我的更新。
  • @Noseratio 很高兴知道。

标签: c# asp.net .net asynchronous async-await


【解决方案1】:

在 ASP.NET 中,WindowsIdentity 不会自动被 AspNetSynchronizationContext 传输,这与 Thread.CurrentPrincipal 不同。每次 ASP.NET 进入新的池线程时,都会保存模拟上下文并将 here 设置为应用程序池用户的上下文。当 ASP.NET 离开线程时,它会恢复hereawait 延续也会发生这种情况,作为延续回调调用的一部分(由AspNetSynchronizationContext.Post 排队的那些)。

因此,如果您想在 ASP.NET 中跨多个线程的等待中保持标识,则需要手动对其进行流动。您可以为此使用本地或类成员变量。或者,您可以通过logical call context 使用.NET 4.6 AsyncLocal&lt;T&gt;Stephen Cleary's AsyncLocal 之类的东西来传输它。

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

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

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

上述方法可行,因为在没有同步上下文的情况下,WindowsIdentity 确实会流经await。它几乎流入the same way as Thread.CurrentPrincipal does,即跨越并流入异步调用(但不在这些调用之外)。我相信这是作为SecurityContext 流的一部分完成的,它本身是ExecutionContext 的一部分,并显示相同的写时复制行为。

为了支持这个说法,我用一个控制台应用程序做了一个小实验:

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 在 cmets 中建议的那样,自动流模拟上下文的另一个选项是在全局 `aspnet.config` 文件中使用`)。

【讨论】:

  • 非常感谢您的深入回答。它确实帮助我理解了传入和传出异步调用的上下文背后的原因。我找到了另一种解决方案,可以更改默认行为并允许将身份传递给异步创建的最终线程。在这里解释:stackoverflow.com/a/10311823/637443。设置: 也应该适用于使用 app.config 的应用程序。使用此配置,身份将被传递并保留。如果不正确,请更正。
  • @PawełForys,我认为单独使用 &lt;alwaysFlowImpersonationPolicy enabled="true"/&gt; 应该可以做到,您可能不需要 legacyImpersonationPolicy。让我们知道这是否有效。
  • 正确,单独 允许身份跨线程流动。谢谢!
  • 如果您不介意,请更新您的答案,正如您所提到的,“如果您想在跨 ASP.NET 中的多个线程的等待中保持身份,您需要手动处理它”。当使用 时,这是自动完成的。谢谢!
  • 开启 alwaysFlowImpersonationPolicy 是否存在安全风险?
【解决方案2】:

看来,如果通过 httpWebRequest 使用模拟异步 http 调用

HttpWebResponse webResponse;
            using (identity.Impersonate())
            {
                var webRequest = (HttpWebRequest)WebRequest.Create(url);
                webResponse = (HttpWebResponse)(await webRequest.GetResponseAsync());
            }

设置&lt;legacyImpersonationPolicy enabled="false"/&gt; 也需要在aspnet.config 中设置。否则 HttpWebRequest 将代表应用程序池用户而不是模拟用户发送。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-25
    • 1970-01-01
    • 2013-12-18
    • 1970-01-01
    相关资源
    最近更新 更多