【问题标题】:.Net Core WindowsIdentity impersonation does not seem to be working.Net Core WindowsIdentity 模拟似乎不起作用
【发布时间】:2020-07-05 11:13:37
【问题描述】:

我有以下代码:

var baseUrl = "https://" + GetIdentityProviderHost(environment) + "/oauth2/authorize";
var query = $"?scope=openid&response_type=code&redirect_uri={redirectUrl}&client_id={clientId}";
var combinedUrl = baseUrl + query;

var currentUser = WindowsIdentity.GetCurrent(); 

await WindowsIdentity.RunImpersonated(currentUser.AccessToken, async() =>
{
    using (var client = new WebClient{ UseDefaultCredentials = true })
    {
        var response = client.DownloadString(combinedUrl);          
        Console.WriteLine(response);
    }
});

它基本上是构造一个 URL,然后调用它。

调用返回 401(未授权)。

但如果我将 combinedUrl 粘贴到 chrome 或 postman 中,它可以完美运行。这告诉我,我的通话可以正常工作,因为 Chrome 正在使用我的 Windows 凭据进行通话。

我添加了WindowsIdentity.RunImpersonated 代码来尝试解决这个问题。不过好像没什么效果。

如何使用集成 Windows 身份验证 (IWA) 拨打网络电话?


详情:

如果我运行以下 cURL 命令,它会起作用:

curl -L --negotiate -u : -b ~/cookiejar.txt "https://myIdp.domain.net/oauth2/authorize?scope=openid&response_type=code&redirect_uri=https://localhost:5001&client_id=my_client_id_here"

我不确定如何在 C# 代码中复制所有这些内容。

仅供参考:我在这个问题中专门询问了这个 cURL 命令(因为这个问题集中在模拟上):Replicate cURL Command Using Redirect and Cookies in .Net Core 3.1

【问题讨论】:

  • 我尝试设置我的项目来重现您的问题。问题是我无法“模拟”您的身份服务器。您的身份服务器/身份验证端点是什么?根据我的经验,我使用 HttpClient 而不是 WebClient 来执行请求,也许这可以解决您的问题(只是一个简短的猜测)?
  • @Martin - 我希望我的问题不是针对我的身份服务器的。更多关于如何制作 cookie 来发送或其他内容。我会共享我的身份服务器端点,但不幸的是我的身份服务器位于防火墙后面。它是一个 WSO2 身份服务器。我已经用 HttpClient 进行了尝试,并得到了相同的结果。 (我展示了WebClient 的用法,因为我看到另一个问题说WebClient 将包含凭据:stackoverflow.com/a/12675503/16241
  • 只是一个盲目的猜测:) ...下一个问题:为什么WindowsIdentity.RunImpersonated前面有一个awaitdocumentation它只是void你没有使用默认 WindowsIdentity 来自 System.Security.Principal 命名空间?
  • @Martin - 嗯,这很奇怪。 LinqPad 中的“悬停信息”显示它正在使用System.Security.Principal。但是您发送的文档链接清楚地表明它只是无效的。不知道该怎么办...
  • 由于您无论如何都想使用当前身份,因此模拟可能不会有助于解决问题。如果您直接在WebClient 中配置凭据而不是依赖默认值,会有什么不同吗?

标签: c# .net-core .net-core-3.1


【解决方案1】:

.Net 5 运行模拟 (我在 Blazor 中运行它)

我花了很多时间来解决这个问题,所以我分享我的发现和我的解决方案,希望能帮助其他人避免痛苦!

调查结果: 在 IIS 上,以下代码获取运行该站点的 IIS Appool 帐户,

var currentUser = WindowsIdentity.GetCurrent();

因此,当您使用 AccessToken 时,您将令牌用于错误的帐户。

我还看到了很多关于使用 IHttpConextAccessor 的引用以及很多关于 this 为 null 的问题。 Microsoft 的这篇文章建议不应该使用它(当然在 Blazor 中) MS-Docs

解决方案: 要让用户模拟使用 AuthenticationStateProvider 并从中获取用户并转换为 WindowsIDentity 以检索 AccessToken。 这适用于控制器和剃须刀组件。 注入 AuthenticationStateProvider,然后在您的方法中使用以下代码:

    var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;
    var userToImpersonate = (WindowsIdentity)user.Identity;
    
    await WindowsIdentity.RunImpersonatedAsync(userToImpersonate.AccessToken, async () => 
  {
    
      // Your Code in here
    
  }

Windows Impersonation 仅适用于 Windows,因此如果您想禁止 Visual Studio 警告,请在代码周围加上以下内容:

#pragma warning disable CA1416 // Validate platform compatibility
...
#pragma warning restore CA1416 // Validate platform compatibility

【讨论】:

  • 如果我尝试这个,我得到一个 castException: 'Unable to cast object of type 'System.Security.Claims.ClaimsIdentity' to type 'System.Security.Principal.WindowsIdentity' 你有什么想法吗为什么会这样?
  • 编辑:当 windowsAuthentication 被禁用时会抛出这个错误 :)
  • 是的,您需要启用 Windows 身份验证才能获取 Windows 身份。
【解决方案2】:

我也有类似的情况。我有一个 mvc 和 web api。两者都使用 windows 身份验证,我需要将当前 mvc 的用户的 windows 身份传递给 api 进行身份验证。我使用.net 5。我做了这个并且它有效。所以在startup.cs中,我添加了这个

services.AddHttpClient("somename", c =>
            {
                c.BaseAddress = new Uri(Configuration.GetValue<string>("baseURL"));
            })
            .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler()
            {
                UseDefaultCredentials = true
            }); 

当我需要使用 httpclient 调用 API 时。我这样做了:

IPrincipal p = _httpContextAccessor.HttpContext.User;
                HttpResponseMessage result=null;
                if(p.Identity is WindowsIdentity wid)
                {
                    await WindowsIdentity.RunImpersonated(wid.AccessToken, async () =>
                     {
                         result = await _client.GetAsync("APIController/Action");
                     });
                }

【讨论】:

    【解决方案3】:

    您的模拟代码没有问题。 你写的是windows App还是Asp.net core APP? 可能存在用户帐户问题,您正在执行代码的用户是标准用户,无法模拟。尝试域用户并授予管理员权限进行测试。 另一个问题是它只能在交互模式下使用。像 控制台应用程序的代码。 // 下面的示例演示了如何使用 WindowsIdentity 类来模拟用户。
    // 重要提示:
    // 此示例要求用户在控制台屏幕上输入密码。
    // 密码将在屏幕上可见,因为控制台窗口
    // 本机不支持屏蔽输入。

    using System;  
    using System.Runtime.InteropServices;  
    using System.Security;  
    using System.Security.Principal;  
    using Microsoft.Win32.SafeHandles;  
    
    public class ImpersonationDemo  
    {  
        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]  
        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,  
            int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);  
    
        public static void Main()  
        {  
            // Get the user token for the specified user, domain, and password using the   
            // unmanaged LogonUser method.   
            // The local machine name can be used for the domain name to impersonate a user on this machine.  
            Console.Write("Enter the name of the domain on which to log on: ");  
            string domainName = Console.ReadLine();  
    
            Console.Write("Enter the login of a user on {0} that you wish to impersonate: ", domainName);  
            string userName = Console.ReadLine();  
    
            Console.Write("Enter the password for {0}: ", userName);  
    
            const int LOGON32_PROVIDER_DEFAULT = 0;  
            //This parameter causes LogonUser to create a primary token.   
            const int LOGON32_LOGON_INTERACTIVE = 2;  
    
            // Call LogonUser to obtain a handle to an access token.   
            SafeAccessTokenHandle safeAccessTokenHandle;  
            bool returnValue = LogonUser(userName, domainName, Console.ReadLine(),  
                LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,  
                out safeAccessTokenHandle);  
    
            if (false == returnValue)  
            {  
                int ret = Marshal.GetLastWin32Error();  
                Console.WriteLine("LogonUser failed with error code : {0}", ret);  
                throw new System.ComponentModel.Win32Exception(ret);  
            }  
    
            Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));  
            // Check the identity.  
            Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);  
    
            // Note: if you want to run as unimpersonated, pass  
            //       'SafeAccessTokenHandle.InvalidHandle' instead of variable 'safeAccessTokenHandle'  
            WindowsIdentity.RunImpersonated(  
                safeAccessTokenHandle,  
                // User action  
                () =>  
                {  
                    // Check the identity.  
                    Console.WriteLine("During impersonation: " + WindowsIdentity.GetCurrent().Name);  
                }  
                );  
    
            // Check the identity again.  
            Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);  
        }  
    }
    

    【讨论】:

      【解决方案4】:

      很遗憾,我无法重现您的问题,使用此代码时模拟对我来说效果很好:

      WindowsIdentity identity = WindowsIdentity.GetCurrent();
      
      using (identity.Impersonate())
      {
          HttpWebRequest request = (HttpWebRequest) WebRequest.Create("https://my-address");
          request.UseDefaultCredentials = true;
      
          HttpWebResponse response = (HttpWebResponse) request.GetResponse();
      }
      

      我仅使用 .NET Framework 对此进行了测试,但由于您已经尝试手动设置 Credentials,我想这不是其中一个 cmets 中提到的 .NET Core 模拟问题。

      所以我的猜测是问题与您尝试访问的地址有关。

      问题可能是重定向,我无法测试,但您可能想尝试this 答案中的解决方案。您可以使用request.AllowAutoRedirect = false,因为默认值为true,在这种情况下,自动重定向时会清除授权标头(MSDN AllowAutoRedirect Property)。

      除此之外,您可能还想尝试使用 request.ImpersonationLevel = TokenImpersonationLevel.Delegation (MSDN ImpersonationLevel Property) 或 request.PreAuthenticate = true (MSDN PreAuthenticate Property)。

      正如我所说,我无法重现问题,所以这些只是一些可能(或可能不)适合你的想法......

      【讨论】:

        【解决方案5】:

        我面前没有 Windows 框,因此我无法彻底验证这一点。但这似乎有点像蛇坑,基于讨论,例如这里(尤其是从这个评论开始): https://github.com/dotnet/runtime/issues/24009#issuecomment-544511572

        关于如何在异步调用中保持身份似乎有各种不同的意见。

        但是,如果您查看该评论中的示例,

        app.Use(async (context, next) =>
        {
            await WindowsIdentity.RunImpersonated(someToken, () => next());
        });
        

        它看起来不像你发送的函数,因为WindowsIdentity.RunImpersonated 的第二个参数应该是异步的。

        你试过了吗:

        var baseUrl = "https://" + GetIdentityProviderHost(environment) + "/oauth2/authorize";
        var query = $"?scope=openid&response_type=code&redirect_uri={redirectUrl}&client_id={clientId}";
        var combinedUrl = baseUrl + query;
        
        var currentUser = WindowsIdentity.GetCurrent(); 
        
        await WindowsIdentity.RunImpersonated(currentUser.AccessToken, () =>
        {
            using (var client = new WebClient{ UseDefaultCredentials = true })
            {
                var response = client.DownloadString(combinedUrl);          
                Console.WriteLine(response);
        
            }
        });
        

        您可以在 WindowsIdentity.RunImpersonated 上找到 Microsoft 文档:https://docs.microsoft.com/en-us/dotnet/api/system.security.principal.windowsidentity.runimpersonated?view=netcore-3.1

        【讨论】:

        • 感谢您的回复。我将其插入,不幸的是仍然得到 401 响应。 (但当我将 combinedUrl 放入 chrome 时,仍然会得到有效的响应。
        • RunImpersonated 的当前实现确实捕获了执行上下文中的标识以恢复异步代码。但这不是这里的问题。这更有可能是可怕的“双跳问题”。
        • 我的问题被错误地集中在我的问题上。我将在这里奖励赏金以感谢您的帮助(因为这是第一个答案)。但对于其他看到这一点的人来说,运行这样的命令的问题并不像模拟调用那么简单。请参阅stackoverflow.com/questions/60998324/…stackoverflow.com/questions/60999574/… 了解更多信息。
        • 感谢您的赏金奖励。抱歉,我无法解决您的原始问题。我没有尝试过使用模拟来自己流动 Windows 身份,所以我只是尝试彻底阅读文档。希望你能解决你原来的问题:)
        猜你喜欢
        • 2020-04-10
        • 2018-03-29
        • 1970-01-01
        • 2018-11-27
        • 1970-01-01
        • 1970-01-01
        • 2019-01-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多