这个似乎是一个很长的机会。 但是我已经看到了几个答案,表明当.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"
此命令的流程如下所示:
- 加载提供的网址(https://idp.domain.net/oauth2/authorize....)
- 获取 302 响应以重定向到 https://idp.domain.net/iwa-kerberos?state=state_guid_here
- 因为
-L
选项在那里,所以它会跟随重定向
- 因为
- 重定向以带有
www-authenticate:Negotiate
标头的 401(未经授权)进行响应。 - cURL 看到
www-authenticate:Negotiate
标头并从操作系统获取 Kerberos 令牌(因为--negotiate
和-u
选项)。 - cURL 调用具有附加标头
Authorization: Negotiate <kerberos token here>
的重定向 URL (https://idp.domain.net/iwa-kerberos?state=state_guid_here)。 - 响应 302 返回重定向到添加了cookie的 https://idp.domain.net/commonauth?state=state_guid_here&iwaauth=1
- 由于
-b
选项,cookie 由 cURL 拾取。
- 由于
- cURL 使用上一步的 302 中返回的 cookie 调用重定向 URL (https://idp.domain.net/commonauth?state=state_guid_here&iwaauth=1)。
- 返回另一个 302 重定向。 重定向至包含更多 Cookie 的https://idp.domain.net/oauth2/authorize?sessionDataKey=session_key_guid_here。(由于
-b
选项,再次被选中。 - 重定向到 https://idp.domain.net/oauth2/authorize?sessionDataKey=session_key_guid_here 后会添加 Cookie。
- 另一个 302 重定向返回到 https://localhost:5001/?code=code_guid_here&session_state=session_state_here(添加 cookie)。
- cURL 使用添加的 cookie 将重定向到 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 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
可以通过为其构造函数提供带有CookieContainer
HttpClientHandler
来存储cookie:
var client = new HttpClient(new HttpClientHandler() { CookieContainer = new CookieContainer() })
这取决于HttpClientHandler
具有UseCookies = true
,但默认情况下是true
的。
返回内容
您可以使用HttpClient.GetAsync
和HttpResponseMessage.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 是自动添加的,但我是否添加了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: 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
系统.线程.任务
系统网