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

C# 在.Net Core 3.1中使用重定向和Cookies复制cURL命令,c#,curl,.net-core,.net-core-3.1,C#,Curl,.net Core,.net Core 3.1,这一个似乎不太可能。但我看到了一些答案,它们表明当.Net核心应用程序需要cURL时,应该使用HttpClient(以及类似的) 我有以下cURL命令(非常有效): 此命令的流程如下所示: 加载提供的url() 获取要重定向到的302响应 因为有-L选项,所以它遵循重定向 重定向用带有www-authenticate:Negotiate头的401(未经授权)响应 cURL看到www-authenticate:Negotiate头并从操作系统获取Kerberos令牌(因为--Negotiat

这一个似乎不太可能。但我看到了一些答案,它们表明当.Net核心应用程序需要cURL时,应该使用HttpClient(以及类似的)

我有以下cURL命令(非常有效):

此命令的流程如下所示:

  • 加载提供的url()
  • 获取要重定向到的302响应
    • 因为有
      -L
      选项,所以它遵循重定向
  • 重定向用带有
    www-authenticate:Negotiate
    头的401(未经授权)响应
  • cURL看到
    www-authenticate:Negotiate
    头并从操作系统获取Kerberos令牌(因为
    --Negotiate
    -u
    选项)
  • cURL调用重定向url(),并附加标题
    Authorization:Negotiate
  • 302的响应返回重定向到,并添加了cookie
    • 由于
      -b
      选项,cookie由cURL拾取
  • cURL调用重定向url(),并使用上一步302中返回的cookie
  • 返回另一个302重定向。正在重定向到包含更多cookie的。(由于
    -b
    选项,再次拾取。)
  • 重定向到后跟着添加的cookie
  • 另一个302重定向返回到https://localhost:5001/?code=code_guid_here&session_state=session_state_here (添加了一个cookie)
  • cURL将重定向到https://localhost:5001/?code=code_guid_here&session_state=session_state_here 加上饼干
  • 内容https://localhost:5001/?code=code_guid_here&session_state=session_state_here 返回到cURL命令行
  • 把这一切写出来,让它在.Net应用程序中运行似乎是一项严肃的任务。但我想我会问,万一它是内置在框架的某个地方

    是否有一个.Net核心框架类(或类似类)可以让我用C#code重现这个cURL命令?

    注意:我可以通过调用powershell来实现这一点。这个问题是关于如何使用
    HttpClient

    将curl标志转换为
    HttpClient
    -L
    HttpClient
    应自动遵循重定向,因为

    --协商-u:
    HttpClient
    将处理协商,如果您为其构造函数提供了一个提供凭据的
    HttpClientHandler
    。由于您将默认Windows凭据与
    -u:
    一起使用,因此可以从以下位置使用代码:

    -b~/cookiejar.txt
    HttpClient
    可以通过向其构造函数提供一个
    HttpClientHandler
    来存储cookie,方法是:

    这取决于
    HttpClientHandler

    返回内容 您可以使用和来读取
    HttpClient的响应

    var response = await client.GetAsync("");
    var content = await response.Content.ReadAsStringAsync();
    
    一切结合在一起 如果我们显式地设置上面引用的每个值,那么构建
    HttpClient
    并发出等效请求应该是这样的:

    var client = new HttpClient(
        new HttpClientHandler()
        {
            // -L
            AllowAutoRedirect = true,
    
            // --negotiate -u :
            UseDefaultCredentials = true,
    
            // -b ~/cookiejar.txt
            CookieContainer = new CookieContainer(),
            UseCookies = true
        }
    );
    
    var response = await client.GetAsync("https://idp.domain.net/oauth2/authorize?scope=openid&response_type=code&redirect_uri=https://localhost:5001&client_id=client_id_here");
    var content = await response.Content.ReadAsStringAsync();
    

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

    Cookies是自动添加的,但我是否添加了
    CookieContainer
    UseCookies
    设置似乎并不重要

    而且,
    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;
        }
    
    }
    
    async Task Main()
    {   
    var services=newservicecolection();
    services.AddHttpClient(“OAuthClient”).ConfigurePrimaryHttpMessageHandler(()=>new AuthenticationHandler());;
    var serviceProvider=services.BuildServiceProvider();
    var httpClientFactory=serviceProvider.GetService();
    var authCodeUrl=”https://idp.domain.net/oauth2/authorize?scope=openid&response_type=code&redirect_uri=https://localhost:5001&client_id=client_id_here";
    var authCongregator=新的身份验证连接器(httpClientFactory);
    var authCode=await authCongregator.GetAuthorizationCodeViaKerberosIwa(authCodeUrl);
    Console.WriteLine(authCode);
    }
    公共类身份验证连接器
    {
    私人IHttpClientFactory httpClientFactory;
    公共身份验证连接器(IHttpClientFactory httpClientFactory)
    {
    this.httpClientFactory=httpClientFactory;
    }
    公共异步任务GetAuthorizationCodeViaKerberosIwa(字符串authCodeUrl)
    {
    var kerberosToken=GetKerberosTokenViaIwa();
    var authCode=等待GetAuthorizationCodeViaKerberos(authCodeUrl,kerberosToken);
    返回认证码;
    }
    公共异步任务GetAuthorizationCodeViaKerberosCredsAsync(字符串authCodeUrl、字符串用户名、字符串密码)
    {
    var kerberosToken=等待getkerberostokenviacredasync(用户名、密码);
    var authCode=等待GetAuthorizationCodeViaKerberos(authCodeUrl,kerberosToken);
    返回认证码;
    }
    公共异步任务GetAuthorizationCodeViaKerberos(字符串authCodeUrl,字符串kerberosToken)
    {
    var httpClient=httpClientFactory.CreateClient(“OAuthClient”);
    var done=false;
    字符串currentUrl=authCodeUrl;
    字符串responseText=“”;
    布尔成功=假;
    而(!完成)
    {           
    var response=wait httpClient.GetAsync(currentUrl);
    responseText=wait response.Content.ReadAsStringAsync();
    //如果已设置,则重置AuthenticationOn标头。(在每次迭代中根据需要进行设置。)
    httpClient.DefaultRequestHeaders.Authorization=null;
    如果(response.StatusCode==HttpStatusCode.Unauthorized
    &&response.Headers.Any(x=>x.Key==“WWW-Authenticate”&&x.Value.Contains(“协商”))
    
    var response = await client.GetAsync("");
    var content = await response.Content.ReadAsStringAsync();
    
    var client = new HttpClient(
        new HttpClientHandler()
        {
            // -L
            AllowAutoRedirect = true,
    
            // --negotiate -u :
            UseDefaultCredentials = true,
    
            // -b ~/cookiejar.txt
            CookieContainer = new CookieContainer(),
            UseCookies = true
        }
    );
    
    var response = await client.GetAsync("https://idp.domain.net/oauth2/authorize?scope=openid&response_type=code&redirect_uri=https://localhost:5001&client_id=client_id_here");
    var content = await response.Content.ReadAsStringAsync();
    
    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;
        }
    
    }