C# 重定向时授权标头丢失
下面是执行身份验证、生成授权标头和调用API的代码 不幸的是,在API上的C# 重定向时授权标头丢失,c#,.net,rest,dotnet-httpclient,C#,.net,Rest,Dotnet Httpclient,下面是执行身份验证、生成授权标头和调用API的代码 不幸的是,在API上的get请求之后,我收到了一个401未经授权的错误 但是,当我捕获Fiddler中的流量并重播它时,对API的调用成功,我可以看到所需的200OK状态代码 [Test] public void RedirectTest() { HttpResponseMessage response; var client = new HttpClient(); using (var authString = new
get
请求之后,我收到了一个401未经授权的错误
但是,当我捕获Fiddler中的流量并重播它时,对API的调用成功,我可以看到所需的200OK
状态代码
[Test]
public void RedirectTest()
{
HttpResponseMessage response;
var client = new HttpClient();
using (var authString = new StringContent(@"{username: ""theUser"", password: ""password""}", Encoding.UTF8, "application/json"))
{
response = client.PostAsync("http://host/api/authenticate", authString).Result;
}
string result = response.Content.ReadAsStringAsync().Result;
var authorization = JsonConvert.DeserializeObject<CustomAutorization>(result);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authorization.Scheme, authorization.Token);
client.DefaultRequestHeaders.Add("Accept", "application/vnd.host+json;version=1");
response =
client.GetAsync("http://host/api/getSomething").Result;
Assert.True(response.StatusCode == HttpStatusCode.OK);
}
[测试]
公共测试()
{
HttpResponseMessage响应;
var client=新的HttpClient();
使用(var authString=newstringcontent(@“{username:“theUser”,“password:“password”“}”,Encoding.UTF8,“application/json”))
{
响应=客户端。PostAsync(“http://host/api/authenticate“,authString)。结果;
}
字符串结果=response.Content.ReadAsStringAsync().result;
var authorization=JsonConvert.DeserializeObject(结果);
client.DefaultRequestHeaders.Authorization=新的AuthenticationHeaderValue(Authorization.Scheme、Authorization.Token);
client.DefaultRequestHeaders.Add(“Accept”,“application/vnd.host+json;version=1”);
回应=
client.GetAsync(“http://host/api/getSomething三、结果;
Assert.True(response.StatusCode==HttpStatusCode.OK);
}
当我运行此代码时,授权标头将丢失
但是,在Fiddler中,该标头已成功传递
你知道我做错了什么吗?你之所以经历这种行为,是因为它是故意的
大多数HTTP客户端(默认情况下)在执行重定向时会删除授权头
原因之一是安全。客户端可能会被重定向到不受信任的第三方服务器,您不想将您的授权令牌透露给该服务器
您可以做的是检测重定向已发生,并直接将请求重新发出到正确的位置
您的API正在返回401 Unauthorized
,以指示授权标头缺失(或不完整)。如果请求中存在授权信息,但只是不正确(错误的用户名/密码),我将假设相同的API返回403禁止
如果是这种情况,您可以检测“重定向/缺少授权标头”组合并重新发送请求
以下是改写问题的代码,用于执行此操作:
[Test]
public void RedirectTest()
{
// These lines are not relevant to the problem, but are included for completeness.
HttpResponseMessage response;
var client = new HttpClient();
using (var authString = new StringContent(@"{username: ""theUser"", password: ""password""}", Encoding.UTF8, "application/json"))
{
response = client.PostAsync("http://host/api/authenticate", authString).Result;
}
string result = response.Content.ReadAsStringAsync().Result;
var authorization = JsonConvert.DeserializeObject<CustomAutorization>(result);
// Relevant from this point on.
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authorization.Scheme, authorization.Token);
client.DefaultRequestHeaders.Add("Accept", "application/vnd.host+json;version=1");
var requestUri = new Uri("http://host/api/getSomething");
response = client.GetAsync(requestUri).Result;
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Authorization header has been set, but the server reports that it is missing.
// It was probably stripped out due to a redirect.
var finalRequestUri = response.RequestMessage.RequestUri; // contains the final location after following the redirect.
if (finalRequestUri != requestUri) // detect that a redirect actually did occur.
{
if (IsHostTrusted(finalRequestUri)) // check that we can trust the host we were redirected to.
{
response = client.GetAsync(finalRequestUri).Result; // Reissue the request. The DefaultRequestHeaders configured on the client will be used, so we don't have to set them again.
}
}
}
Assert.True(response.StatusCode == HttpStatusCode.OK);
}
private bool IsHostTrusted(Uri uri)
{
// Do whatever checks you need to do here
// to make sure that the host
// is trusted and you are happy to send it
// your authorization token.
if (uri.Host == "host")
{
return true;
}
return false;
}
[测试]
公共测试()
{
//这些行与问题无关,但为了完整性而包括在内。
HttpResponseMessage响应;
var client=新的HttpClient();
使用(var authString=newstringcontent(@“{username:“theUser”,“password:“password”“}”,Encoding.UTF8,“application/json”))
{
响应=客户端。PostAsync(“http://host/api/authenticate“,authString)。结果;
}
字符串结果=response.Content.ReadAsStringAsync().result;
var authorization=JsonConvert.DeserializeObject(结果);
//从这一点上讲是相关的。
client.DefaultRequestHeaders.Authorization=新的AuthenticationHeaderValue(Authorization.Scheme、Authorization.Token);
client.DefaultRequestHeaders.Add(“Accept”,“application/vnd.host+json;version=1”);
var requestUri=新Uri(“http://host/api/getSomething");
response=client.GetAsync(requestUri).Result;
if(response.StatusCode==HttpStatusCode.Unauthorized)
{
//已设置授权标头,但服务器报告缺少该标头。
//它可能是由于重定向而被剥离的。
var finalRequestUri=response.RequestMessage.RequestUri;//包含重定向后的最终位置。
if(finalRequestUri!=requestUri)//检测到确实发生了重定向。
{
if(IsHostTrusted(finalRequestUri))//检查我们是否可以信任重定向到的主机。
{
response=client.GetAsync(finalRequestUri).Result;//重新发出请求。将使用在客户端上配置的DefaultRequestHeader,因此我们不必再次设置它们。
}
}
}
Assert.True(response.StatusCode==HttpStatusCode.OK);
}
私有bool-IsHostTrusted(Uri)
{
//在这里做你需要做的任何检查
//确保主机
//是值得信任的,您很乐意发送
//您的授权令牌。
如果(uri.Host==“主机”)
{
返回true;
}
返回false;
}
请注意,您可以保存finalRequestUri
的值,并将其用于将来的请求,以避免重试中涉及的额外请求。但是,由于这是一个临时重定向,您可能每次都应该向原始位置发出请求。我也遇到过类似的问题,但不完全相同。在我的例子中,我也有重定向问题,但安全性是通过OAuth实现的,OAuth也有次要但相关的问题,即令牌有时会过期
因此,我希望能够将HttpClient
配置为在收到401未经授权的
响应时自动转到并刷新OAuth令牌,而不管这是因为重定向还是令牌过期
Chris O'Neill发布的解决方案显示了要采取的一般步骤,但我想将这种行为嵌入到HttpClient
对象中,而不是必须用命令式检查来包围所有HTTP代码。我们有很多使用共享HttpClient
对象的现有代码,因此如果我可以更改该对象的行为,那么重构代码就会容易得多
下面的代码看起来正在运行。到目前为止,我只制作了原型,但它似乎在工作。我们的大部分代码库都是F#格式的,因此代码是F#:
Chris O'Neill发布的答案仔细检查了新URL是否仍然被认为是安全的。我跳过了这里的安全考虑,但是在重试请求之前,您应该强烈考虑包括类似的检查。
open System.Net
open System.Net.Http
type TokenRefresher (refreshAuth, inner) =
inherit MessageProcessingHandler (inner)
override __.ProcessRequest (request, _) = request
override __.ProcessResponse (response, cancellationToken) =
if response.StatusCode <> HttpStatusCode.Unauthorized
then response
else
response.RequestMessage.Headers.Authorization <- refreshAuth ()
inner.SendAsync(response.RequestMessage, cancellationToken).Result
let client = new HttpClient(new TokenRefresher(refreshAuth, new HttpClientHandler ()))
public class TemporaryRedirectHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.TemporaryRedirect)
{
var location = response.Headers.Location;
if (location == null)
{
return response;
}
using (var clone = await CloneRequest(request, location))
{
response = await base.SendAsync(clone, cancellationToken);
}
}
return response;
}
private async Task<HttpRequestMessage> CloneRequest(HttpRequestMessage request, Uri location)
{
var clone = new HttpRequestMessage(request.Method, location);
if (request.Content != null)
{
clone.Content = await CloneContent(request);
if (request.Content.Headers != null)
{
CloneHeaders(clone, request);
}
}
clone.Version = request.Version;
CloneProperties(clone, request);
CloneKeyValuePairs(clone, request);
return clone;
}
private async Task<StreamContent> CloneContent(HttpRequestMessage request)
{
var memstrm = new MemoryStream();
await request.Content.CopyToAsync(memstrm).ConfigureAwait(false);
memstrm.Position = 0;
return new StreamContent(memstrm);
}
private void CloneHeaders(HttpRequestMessage clone, HttpRequestMessage request)
{
foreach (var header in request.Content.Headers)
{
clone.Content.Headers.Add(header.Key, header.Value);
}
}
private void CloneProperties(HttpRequestMessage clone, HttpRequestMessage request)
{
foreach (KeyValuePair<string, object> prop in request.Properties)
{
clone.Properties.Add(prop);
}
}
private void CloneKeyValuePairs(HttpRequestMessage clone, HttpRequestMessage request)
{
foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers)
{
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
}
var handler = new TemporaryRedirectHandler()
{
InnerHandler = new HttpClientHandler()
{
AllowAutoRedirect = false
}
};
HttpClient client = new HttpClient(handler);