在 .Net Core 3.1 中使用重定向和 Cookie 复制 cURL 命令

Vac*_*ano 6 c# curl .net-core .net-core-3.1

这似乎是一个很遥远的事情。但我看到几个答案表明,当 .Net Core 应用程序中需要 cURL 时,应该使用 HttpClient (和类似的)。

我有以下 cURL 命令(完美运行):

curl -v -L --negotiate -u : -b ~/cookiejar.txt  "https://idp.domain.net/oauth2/authorize?scope=openid&response_type=code&redirect_uri=https://localhost:5001&client_id=client_id_here"
Run Code Online (Sandbox Code Playgroud)

该命令的流程如下:

  1. 加载提供的 url ( https://idp.domain.net/oauth2/authorize.... )
  2. 获取 302 响应以重定向到https://idp.domain.net/iwa-kerberos?state=state_guid_here
    • 因为该-L选项在那里,所以它遵循重定向
  3. 重定向响应带有www-authenticate:Negotiate标头的 401(未经授权)。
  4. cURL 查看www-authenticate:Negotiate标头并从操作系统获取 Kerberos 令牌(因为--negotiate-u选项)。
  5. cURL 调用重定向 URL ( https://idp.domain.net/iwa-kerberos?state=state_guid_here ),并附加一个Authorization: Negotiate <kerberos token here>.
  6. 302 响应返回重定向到https://idp.domain.net/commonauth?state=state_guid_here&iwaauth=1添加了 cookie
    • 由于该-b选项,cURL 会拾取 cookie。
  7. cURL使用上一步的 302 中返回的 cookie调用重定向 url ( https://idp.domain.net/commonauth?state=state_guid_here&iwaauth=1 ) 。
  8. 返回另一个 302 重定向。重定向到https://idp.domain.net/oauth2/authorize?sessionDataKey=session_key_guid_here 并带有更多 cookie。(因为这个-b选项再次被选中。)
  9. 重定向到https://idp.domain.net/oauth2/authorize?sessionDataKey=session_key_guid_here后跟添加的 cookie
  10. 另一个 302 重定向返回到 https://localhost:5001/?code=code_guid_here&session_state=session_state_here (带有添加的 cookie)。
  11. cURL 使用添加的 cookie 重定向到 https://localhost:5001/?code=code_guid_here&session_state=session_state_here。
  12. https://localhost:5001/?code=code_guid_here&session_state=session_state_here 的内容返回到 cURL 命令行。

全部写下来,让它在 .Net 应用程序中运行似乎是一项严肃的任务。但我想我会问一下它是否内置在框架中的某个地方。

是否有 .Net Core Framework 类(或类似的类)可以让我在 C# 代码中重现此 cURL 命令?

注意:我可以通过调用 powershell 来做到这一点。这个问题是关于用 来做的HttpClient

Vac*_*ano 1

我能够构建一个自定义目的方法来完成我需要的调用(以获取 OAuth 2 身份验证代码)。

Cookie 是自动添加的,但我是否添加CookieContainerUseCookies设置似乎并不重要。

而且,UseDefaultCredentials似乎也没有做什么。

async Task Main()
{   
    var services = new ServiceCollection();
    services.AddHttpClient("OAuthClient").ConfigurePrimaryHttpMessageHandler(() => new AuthenticationHandler());;
    var serviceProvider = services.BuildServiceProvider();  
    var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();   
    
    var authCodeUrl = "https://idp.domain.net/oauth2/authorize?scope=openid&response_type=code&redirect_uri=https://localhost:5001&client_id=client_id_here";
    var authNegotiator = new AuthenticaitonNegotiator(httpClientFactory);
    
    var authCode = await authNegotiator.GetAuthorizationCodeViaKerberosIwa(authCodeUrl);    
    Console.WriteLine(authCode);
}


public class AuthenticaitonNegotiator
{
    private IHttpClientFactory httpClientFactory;
    
    public AuthenticaitonNegotiator(IHttpClientFactory httpClientFactory)
    {
        this.httpClientFactory = httpClientFactory; 
    }
    
    public async Task<string> GetAuthorizationCodeViaKerberosIwa(string authCodeUrl)
    {
        var kerberosToken = GetKerberosTokenViaIwa();
        var authCode = await GetAuthorizationCodeViaKerberos(authCodeUrl, kerberosToken);
        return authCode;
    }

    public async Task<string> GetAuthorizationCodeViaKerberosCredsAsync(string authCodeUrl, string username, string password)
    {
        var kerberosToken = await GetKerberosTokenViaCredsAsync(username, password);
        var authCode = await GetAuthorizationCodeViaKerberos(authCodeUrl, kerberosToken);
        return authCode;
    }

    public async Task<string> GetAuthorizationCodeViaKerberos(string authCodeUrl, string kerberosToken)
    {
        var httpClient = httpClientFactory.CreateClient("OAuthClient");
        
        var done = false;
        string currentUrl = authCodeUrl;
        string responseText = "";
        bool wasSuccessful = false;
        while (!done)
        {           
            var response = await httpClient.GetAsync(currentUrl);
            responseText = await response.Content.ReadAsStringAsync();
            // Reset the authenticaiton header if it was set.  (It gets set as needed on each iteration.)
            httpClient.DefaultRequestHeaders.Authorization = null;          

            if (response.StatusCode == HttpStatusCode.Unauthorized
                && response.Headers.Any(x => x.Key == "WWW-Authenticate" && x.Value.Contains("Negotiate")))
            {
                currentUrl = response.RequestMessage.RequestUri.AbsoluteUri;                                        
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Negotiate", kerberosToken);
            }
            else if (response.StatusCode == HttpStatusCode.Redirect)
            {
                var redirectUri = response.Headers.Location;
                var query = HttpUtility.ParseQueryString(redirectUri.Query);
                var code = query.Get("code");
                if (code == null)
                {
                    currentUrl = redirectUri.AbsoluteUri;
                }
                else
                {
                    // If this is the last redirect where we would send to the callback, just grab the auth code.
                    // This saves us from needing to host a service to handle the callback.
                    responseText = code;
                    done = true;
                    wasSuccessful = true;
                }
            }
            else
            {
                done = true;
                wasSuccessful = false;
            }
        }
        
        if (wasSuccessful == false) 
        {
            throw new ApplicationException($"Failed to retrive authorization code: \r\n {responseText}");
        }
        
        return responseText;        
    }

    public async Task<String> GetKerberosTokenViaCredsAsync(string username, string password)
    {
        var client = new KerberosClient();
        var kerbCred = new KerberosPasswordCredential(username, password, "YourDomain.net");
        await client.Authenticate(kerbCred);
        var ticket = await client.GetServiceTicket("http/ServerToGetTheKerberosToken.YourDomain.net");
        return Convert.ToBase64String(ticket.EncodeGssApi().ToArray());
    }

    public string GetKerberosTokenViaIwa()
    {
        string token = "";
        using (var context = new SspiContext($"http/ServerToGetTheKerberosToken.YourDomain.net", "Negotiate"))
        {
            var tokenBytes = context.RequestToken();

            token = Convert.ToBase64String(tokenBytes);
        }
        return token;
    }
}


public class AuthenticationHandler : HttpClientHandler
{
    public AuthenticationHandler()
    {       
        // cURL Equivilant: -L
        AllowAutoRedirect = true;
        MaxAutomaticRedirections = 100;

        // cURL Equivilant: --negotiate -u :
        UseDefaultCredentials = true;

        // cURL Equivilant: -b ~/cookiejar.txt
        CookieContainer = new CookieContainer();
        UseCookies = true;
    }

}
Run Code Online (Sandbox Code Playgroud)

如果您添加以下 NuGet 包,它将在 LinqPad 中运行:

  • Kerberos.NET
  • Microsoft.Extensions.DependencyInjection
  • Microsoft.Extensions.Http

我还有以下“使用”声明:

  • 系统

  • 系统.集合

  • 系统.集合.通用

  • 系统数据

  • 系统诊断

  • 系统IO

  • 系统Linq

  • System.Linq.Expressions

  • 系统.反射

  • 系统.文本

  • 系统.文本.正则表达式

  • 系统线程

  • 系统.交易

  • 系统.Xml

  • 系统.Xml.Linq

  • 系统.Xml.XPath

  • Kerberos.NET

  • Kerberos.NET.客户端

  • Kerberos.NET.凭据

  • Kerberos.NET.实体

  • Kerberos.NET.Win32

  • Microsoft.Extensions.DependencyInjection

  • Microsoft.Extensions.Http

  • 系统网

  • 系统.Net.Http

  • System.Net.Http.Headers

  • 系统.线程.任务

  • 系统.Web