【问题标题】:HttpClient not sending basic authentication after redirect重定向后HttpClient不发送基本身份验证
【发布时间】:2013-10-29 18:26:25
【问题描述】:

我的代码正在对需要基本身份验证的 Web 服务 URL 进行 HTTP GET。

我使用 HttpClient 和定义了 Credentials 属性的 HttpClientHandler 实现了这一点。

这一切都很完美。除了我将经过身份验证的 GET 的用例之一: http://somedomain.com 重定向到 http://www.somedomain.com

似乎 HttpClientHandler 在重定向期间清除了身份验证标头。我怎样才能防止这种情况?无论重定向如何,我都希望发送凭据。

这是我的代码:

// prepare the request
var request = new HttpRequestMessage(method, url);
using (var handler = new HttpClientHandler { Credentials = new NetworkCredential(username, password) , PreAuthenticate = true })
using (var client = new HttpClient(handler))
{
    // send the request
    var response = await client.SendAsync(request);

注意:这是一个相关的问题: Keeping HTTP Basic Authentification alive while being redirected 但由于我使用不同的类来发出请求,可能会有更好、更具体的解决方案

【问题讨论】:

  • 旁注,我认为在这种情况下设计的行为没有意义。我将凭据设置为客户端的一部分,而不是根据特定的 URI(请求)。由于同一个客户端可以执行多个请求,并且无论它们的 URI 是什么都会发送授权,这很愚蠢

标签: c# .net authentication redirect dotnet-httpclient


【解决方案1】:

默认的 HttpClientHandler 在后台使用相同的 HttpWebRequest 基础结构。不要将 NetworkCredential 分配给 Credentials 属性,而是创建一个 CredentialCache 并分配它。

这是我用来代替 AutoRedirect 的方法,加上一点 async/await 仙尘,它可能会更漂亮、更可靠。

 public class GlobalRedirectHandler : DelegatingHandler {

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
        var tcs = new TaskCompletionSource<HttpResponseMessage>();

        base.SendAsync(request, cancellationToken)
            .ContinueWith(t => {
                HttpResponseMessage response;
                try {
                    response = t.Result;
                }
                catch (Exception e) {
                    response = new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
                    response.ReasonPhrase = e.Message;
                }
                if (response.StatusCode == HttpStatusCode.MovedPermanently
                    || response.StatusCode == HttpStatusCode.Moved
                    || response.StatusCode == HttpStatusCode.Redirect
                    || response.StatusCode == HttpStatusCode.Found
                    || response.StatusCode == HttpStatusCode.SeeOther
                    || response.StatusCode == HttpStatusCode.RedirectKeepVerb
                    || response.StatusCode == HttpStatusCode.TemporaryRedirect

                    || (int)response.StatusCode == 308) 
                {

                    var newRequest = CopyRequest(response.RequestMessage);

                    if (response.StatusCode == HttpStatusCode.Redirect 
                        || response.StatusCode == HttpStatusCode.Found
                        || response.StatusCode == HttpStatusCode.SeeOther)
                    {
                        newRequest.Content = null;
                        newRequest.Method = HttpMethod.Get;

                    }
                    newRequest.RequestUri = response.Headers.Location;

                    base.SendAsync(newRequest, cancellationToken)
                        .ContinueWith(t2 => tcs.SetResult(t2.Result));
                }
                else {
                    tcs.SetResult(response);
                }
            });

        return tcs.Task;
    }

    private static HttpRequestMessage CopyRequest(HttpRequestMessage oldRequest) {
        var newrequest = new HttpRequestMessage(oldRequest.Method, oldRequest.RequestUri);

        foreach (var header in oldRequest.Headers) {
            newrequest.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }
        foreach (var property in oldRequest.Properties) {
            newrequest.Properties.Add(property);
        }
        if (oldRequest.Content != null) newrequest.Content = new StreamContent(oldRequest.Content.ReadAsStreamAsync().Result);
        return newrequest;
    }
}

【讨论】:

  • CredentialCache 的问题是我需要过早地知道 URI 后重定向 - 我不知道。 API 端点可由用户配置,这些用户可能会忘记提供 www,或者某天决定购买新域。
  • @talkol 好的。然后关闭自动重定向并编写您自己的消息处理程序来执行重定向。这很容易做到。请注意将这些凭据发送到任意站点的安全问题。
  • 是的,谢谢,这就是我最终所做的。当然是 async-await 仙尘 ;)
  • @DarrelMiller 请看我的回答,我稍微改进了您的代码,还包括一个简单的检查以避免将凭据发送到不同的主机。
【解决方案2】:

我使用了@DarrelMiller 的解决方案,它有效。不过,我做了一些改进

我重构了代码,所以所有内容都在 CopyRequest 中,现在将 response 作为参数。

var newRequest = CopyRequest(response);

base.SendAsync(newRequest, cancellationToken)
    .ContinueWith(t2 => tcs.SetResult(t2.Result));

这是我改进后的 CopyRequest 方法

  • 不是为Redirect / Found / SeeOther 创建一个新的StreamContent 并将其设置为null,而是仅在必要时设置内容。
  • RequestUri 仅在设置了 Location 并考虑到它可能不是相对 uri 时才设置。
  • 最重要的是:我检查新的 Uri,如果主机不匹配,我不会复制自动化标头,以防止将您的凭据泄露给外部主机。
private static HttpRequestMessage CopyRequest(HttpResponseMessage response)
{
    var oldRequest = response.RequestMessage;

    var newRequest = new HttpRequestMessage(oldRequest.Method, oldRequest.RequestUri);

    if (response.Headers.Location != null)
    {
        if (response.Headers.Location.IsAbsoluteUri)
        {
            newRequest.RequestUri = response.Headers.Location;
        }
        else
        {
            newRequest.RequestUri = new Uri(newRequest.RequestUri, response.Headers.Location);
        }
    }

    foreach (var header in oldRequest.Headers)
    {
        if (header.Key.Equals("Authorization", StringComparison.OrdinalIgnoreCase) && !(oldRequest.RequestUri.Host.Equals(newRequest.RequestUri.Host)))
        {
            //do not leak Authorization Header to other hosts
            continue;
        }
        newRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);
    }

    foreach (var property in oldRequest.Properties)
    {
        newRequest.Properties.Add(property);
    }

    if (response.StatusCode == HttpStatusCode.Redirect
        || response.StatusCode == HttpStatusCode.Found
        || response.StatusCode == HttpStatusCode.SeeOther)
    {
        newRequest.Content = null;
        newRequest.Method = HttpMethod.Get;
    }
    else if (oldRequest.Content != null)
    {
        newRequest.Content = new StreamContent(oldRequest.Content.ReadAsStreamAsync().Result);
    }

    return newRequest;
}

【讨论】:

  • 哦,谢谢。我没有意识到我原来的解决方案没有主机检查。我应该确保我们的 Graph 中间件包含您的优化 github.com/microsoftgraph/msgraph-sdk-dotnet-core/blob/dev/src/…
  • 感谢您将代码指向我。这解决了我遇到的另一个问题(特别是 CloneAsync 扩展方法):// HttpClient doesn't rewind streams and we have to explicitly do so.。另外我看起来代码已经有了propper host check。
猜你喜欢
  • 2013-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 2015-05-13
  • 1970-01-01
相关资源
最近更新 更多