HttpClientHandler RFC 7616 摘要身份验证 Header 使用错误的 URI

HttpClientHandler RFC 7616 Digest Authentication Header Using Wrong Uri

我正在尝试访问 Lighttpd 服务器上的资源,该服务器强制要求完整请求 URI 与授权请求 header 中的 URI 相匹配。这在 RFC 7616

中指定

The authenticating server MUST assure that the resource designated by the "uri" parameter is the same as the resource specified in the Request-Line; if they are not, the server SHOULD return a 400 Bad Request error. (Since this may be a symptom of an attack, server
implementers may want to consider logging such errors.) The purpose
of duplicating information from the request URL in this field is to
deal with the possibility that an intermediate proxy may alter the
client's Request-Line. This altered (but presumably semantically
equivalent) request would not result in the same digest as that
calculated by the client.

我正在使用 Flurl library (v1.4),它只是 HttpClient 的包装器。但是,HttpClientHandler 来自.Net。

为什么它使用基本 URI 而不是完整 URI?这是一个错误吗?我如何让它使用完整的 URI?

我想在管道中添加另一个 HttpMessageHandler 并使用完整的 URI 修改 Authentication header,但是 HttpClientHandler 不允许您设置 InnerHandler.

完整的 Uri 应该是: http:///base/resource.cgi?my=1¶ms=2

但这就是请求中出现的内容 header:

Authorization: Digest username="user",realm="serve",nonce="5b911545:eaf4352d2113e5e4b1ca253bd70fd90a", uri="/base/resource.cgi",cnonce="bf7cf40f1289bc10bd07e8bf4784159c",nc=00000001,qop="auth",response="cf3c731ec93f7e5928f19f880f8325ab"

导致 400 错误请求 响应。

我的 Flurl 代码:

HttpClient 工厂

    /// <summary>
    /// Custom factory to generate HttpClient and handler to use digest authentication and a continuous stream
    /// </summary>
    private class DigestHttpFactory : Flurl.Http.Configuration.DefaultHttpClientFactory
    {
        private CredentialCache CredCache { get; set; }

        public DigestHttpFactory(string url, string username, string password) : base()
        {
            Url = url;

            CredCache = new CredentialCache
            {
                { new Uri(Url), "Digest", new NetworkCredential(username, password) }
            };
        }

        private string Url { get; set; }

        public override HttpClient CreateHttpClient(HttpMessageHandler handler)
        {
            var client = base.CreateHttpClient(handler);
            client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite); // To keep stream open indefinietly
            return client;
        }

        public override HttpMessageHandler CreateMessageHandler()
        {
            var handler = new HttpClientHandler
            {
                Credentials = CredCache.GetCredential(new Uri(Url), "Digest")
            };


            return handler;
        }
    }

请求码

public class MyConnection
{
    public string BaseUrl => "http://base/resource.cgi";

    public async Task ConnectAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        ConnectionCancellation = new CancellationTokenSource();

        var url = BaseUrl
            .SetQueryParam("my", 1)
            .SetQueryParam("params", 2)

        FlurlHttp.ConfigureClient(url, client =>
        {
            client.Configure(settings =>
            {
                settings.HttpClientFactory = new DigestHttpFactory(url, Username, Password);
            });
        });

        try
        {
            using (var getResponse = url.GetAsync(cancellationToken, HttpCompletionOption.ResponseHeadersRead))
            {
                var responseMessage = await getResponse;

                using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ConnectionCancellation.Token, cancellationToken))
                using (var stream = await responseMessage.Content.ReadAsStreamAsync())
                    await Process(stream, linkedCts.Token);
            }
        }
        catch (OperationCanceledException ex)
        {
            throw ex;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

看来这确实是 bug 微软的 RFC 实现:

System.Net classes don't include the query string in the 'Uri' attribute of the digest authentication header. This is a violation of the RFC, and some web server implementations reject those requests.

post 详细介绍了从 this answer 中获取的解决方法。