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



这个似乎是一个很长的机会。 但是我已经看到了几个答案,表明当.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"

此命令的流程如下所示:

  1. 加载提供的网址(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 调用具有附加标头Authorization: Negotiate <kerberos token here>的重定向 URL (https://idp.domain.net/iwa-kerberos?state=state_guid_here)。
  6. 响应 302 返回重定向到添加了cookie的 https://idp.domain.net/commonauth?state=state_guid_here&iwaauth=1
    • 由于-b选项,cookie 由 cURL 拾取。
  7. cURL 使用上一步的 302 中返回的 cookie 调用重定向 URL (https://idp.domain.net/commonauth?state=state_guid_here&iwaauth=1)。
  8. 返回另一个 302 重定向。 重定向至包含更多 Cookie 的https://idp.domain.net/oauth2/authorize?sessionDataKey=session_key_guid_here。(由于-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 命令?

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

将卷曲标志转换为HttpClient

-L

HttpClient应该自动跟随重定向,因为HttpClientHandler.AllowAutoRedirect默认为true

--negotiate -u :

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

var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });

-b ~/cookiejar.txt

HttpClient可以通过为其构造函数提供带有CookieContainerHttpClientHandler来存储cookie:

var client = new HttpClient(new HttpClientHandler() { CookieContainer = new CookieContainer() })

这取决于HttpClientHandler具有UseCookies = true,但默认情况下是true的。

返回内容

您可以使用HttpClient.GetAsyncHttpResponseMessage.Content来读取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 身份验证代码)。

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: rn {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;
}
}

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

  • Kerberos.NET
  • Microsoft.扩展.依赖注入
  • Microsoft.Extensions.Http

我也有以下"使用"语句:

  • 系统

  • 系统.集合

  • System.Collections.Generic

  • 系统.数据

  • 系统诊断

  • System.IO

  • System.Linq

  • System.Linq.Expressions

  • 系统.反射

  • 系统文本

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

  • 系统线程

  • 系统.事务

  • System.Xml

  • System.Xml.Linq

  • System.Xml.XPath

  • Kerberos.NET

  • Kerberos.NET.Client

  • Kerberos.NET.Credentials

  • Kerberos.NET.Entities

  • Kerberos.NET.Win32

  • Microsoft.扩展.依赖

    注入
  • Microsoft.Extensions.Http

  • System.Net

  • System.Net.Http

  • System.Net.Http.Headers

  • 系统.线程.任务

  • 系统网

最新更新