如何使用 POST、PUT 和 PATCH 进行 DIGEST

How to do DIGEST with POST, PUT and PATCH

我在这里完全不知所措。我目前成功地针对实现 DIGEST MD5-sess 身份验证的 Web 服务执行 GET 请求。这很好用。我得到了预期的结果,所以我认为我的 'BUILD DIGEST AUTH HEADER' 方法按预期工作。

然后我得到了支持 POST、PUT 和 PATCH 的要求,但现在我 运行 遇到了一大堆问题。第一个请求显然是 returns a 401,我阅读了 WWW-Authenticate header 中的信息并进行了 MD5-sess auth header 考虑到这现在是 POST、PUT 或 PATCH 请求。

        var methodString = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
        var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", methodString, dir));

我保持一切不变,但显然也将内容添加到 POST、PUT 和 PATCH 的请求中。但出于某种原因,我无法弄清楚我被锁定在服务之外。第二个请求 return 另一个 401 even.

当方法是 POST、我缺少的 PUT 和 PATCH 时,是否需要为 DIGEST Auth 做一些特殊的事情?

我在 Postman 中设置了所有请求,而且 Postman 没有同样的问题。 Postman 的第二次调用获得了预期的结果。 A 200 和 POST、PUT 和 PATCH 上的预期数据。

我使用的是稍微修改过的版本 DigestAuthFixer.cs,也可以在 Whosebug 中的其他一些帖子中找到。

下面的代码目前被硬编码为使用 MD5-sess 方法。

public class DigestAuthFixer
{
    private static readonly Random random = new Random(DateTime.Now.Millisecond);
    private readonly AuthData authData;
    readonly UrlResolverStrategyBase urlResolverStrategy;

    public HttpStatusCode StatusCode { get; private set; }

    public DigestAuthFixer(BasicAuth basicAuth, UrlResolverStrategyBase urlResolverStrategy)
    {
        // TODO: Complete member initialization
        authData = new AuthData
        {
            Host = urlResolverStrategy.GetHostName(),
            User = basicAuth.GetUsername(),
            Password = basicAuth.GetPassword(),
        };

        this.urlResolverStrategy = urlResolverStrategy;
    }

    private string CalculateMd5Hash(string input)
    {
        var inputBytes = Encoding.ASCII.GetBytes(input);
        var hash = MD5.Create().ComputeHash(inputBytes);
        var sb = new StringBuilder();
        foreach (var b in hash)
        {
            sb.Append(b.ToString("x2"));
        }
        return sb.ToString();
    }

    private string GrabHeaderVar(string varName, string header)
    {
        var regHeader = new Regex(string.Format(@"{0}=""([^""]*)""", varName));
        var matchHeader = regHeader.Match(header);
        if (matchHeader.Success)
        {
            return matchHeader.Groups[1].Value;
        }
        throw new ApplicationException(string.Format("Header {0} not found", varName));
    }

    private string GetDigestHeader(string dir)
    {
        authData.NC++;

        string ha1;
        if (authData.Algorithm == "MD5-sess")
        {
            var ha0 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", authData.User, authData.Realm, authData.Password));
            ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", ha0, authData.Nonce, authData.Cnonce));
        }
        else
        {
            ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", authData.User, authData.Realm, authData.Password));
        }

        var methodString = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
        var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", methodString, dir));

        var digestResponse = CalculateMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, authData.Nonce, authData.NC, authData.Cnonce, authData.Qop, ha2));

        var authString = string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", algorithm=\"{4}\", qop={5}, nc={6:00000000}, cnonce=\"{7}\", response=\"{8}\"", authData.User, authData.Realm, authData.Nonce, dir, authData.Algorithm, authData.Qop, authData.NC, authData.Cnonce, digestResponse);

        return authString;
    }

    public string GrabResponse(string nUrl, string content)
    {   
        var uri = new Uri(authData.Host + nUrl);

        var request = (HttpWebRequest)WebRequest.Create(uri);

        request.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();

        // If we've got a recent Auth header, re-use it!
        if (!string.IsNullOrEmpty(authData.Cnonce) && DateTime.Now.Subtract(authData.CnonceDate).TotalHours < 1.0)
        {
            request.Headers.Add("Authorization", GetDigestHeader(nUrl));
        }

        if (!string.IsNullOrEmpty(urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue))
        {
            request.Headers.Add(HttpHelper.IfMatchHeaderName, urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue);
        }

        AddContentToBody(request, content);

        HttpWebResponse response = null;
        try
        {
            response = (HttpWebResponse)request.GetResponse();
            StatusCode = response.StatusCode;
        }
        catch (WebException ex)
        {
            // Try to fix a 401 exception by adding a Authorization header
            if (ex.Response == null || ((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized)
                throw;

            var wwwAuthenticateHeader = ex.Response.Headers["WWW-Authenticate"];

            authData.Realm = GrabHeaderVar("realm", wwwAuthenticateHeader);
            authData.Nonce = GrabHeaderVar("nonce", wwwAuthenticateHeader);
            authData.Qop = GrabHeaderVar("qop", wwwAuthenticateHeader);
            authData.Algorithm = "MD5-sess"; // GrabHeaderVar("algorithm", wwwAuthenticateHeader);

            authData.NC = 0;
            authData.Cnonce = RandomString(8);
            authData.CnonceDate = DateTime.Now;

            string debug = wwwAuthenticateHeader + Environment.NewLine + nUrl + Environment.NewLine + uri.ToString() + Environment.NewLine + authData.ToString();
            var digestRequest = (HttpWebRequest)WebRequest.Create(uri);
            digestRequest.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();

            AddContentToBody(digestRequest, content);

            var authHeader = GetDigestHeader(nUrl);

            debug += uri.ToString() + Environment.NewLine;
            debug += nUrl + Environment.NewLine;
            debug += authHeader + Environment.NewLine;

            digestRequest.Headers.Add("Authorization", authHeader);

            if (!string.IsNullOrEmpty(urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue))
            {
                request.Headers.Add(HttpHelper.IfMatchHeaderName, urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue);
            }

            HttpWebResponse digestResponse = null;
            try
            {
                //return authHeader;
                digestResponse = (HttpWebResponse)digestRequest.GetResponse();
                StatusCode = digestResponse.StatusCode;
                response = digestResponse;
            }
            catch (Exception digestRequestEx)
            {
                if (digestResponse != null)
                {
                    StatusCode = response.StatusCode;
                }
                else
                {
                    StatusCode = HttpStatusCode.InternalServerError;
                }

                //return "It broke" + Environment.NewLine + debug;
                return "There was a problem with url, username or password (" + digestRequestEx.Message + ")";
            }
        }
        var reader = new StreamReader(response.GetResponseStream());
        return reader.ReadToEnd();
    }

    public string RandomString(int length)
    {
        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var stringChars = new char[length];

        for (int i = 0; i < stringChars.Length; i++)
        {
            stringChars[i] = chars[random.Next(chars.Length)];
        }

        return new string(stringChars);
    }

    private void AddContentToBody(HttpWebRequest request, string content)
    {
        if (string.IsNullOrEmpty(content))
            return;

        var data = Encoding.Default.GetBytes(content); // note: choose appropriate encoding

        request.ContentLength = data.Length;
        request.ContentType = HttpHelper.MediaTypes.Json;
        request.Accept = "*/*";
        request.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
        //request.Headers.Add("Accept-Encoding", "gzip, deflate, br");

        using (var streamWriter = new StreamWriter(request.GetRequestStream()))
        {
            streamWriter.Write(content);
        }
    }
}

internal class AuthData
{
    public string Host;
    public string User;
    public string Password;
    public string Realm;
    public string Nonce;
    public string Qop;
    public string Cnonce;
    public DateTime CnonceDate;
    public int NC;
    public string Algorithm;

    public override string ToString()
    {
        string newLine = Environment.NewLine;

        string result = Host + newLine;
        result += User + newLine;
        result += Realm + newLine;
        result += Nonce + newLine;
        result += Qop + newLine;
        result += Cnonce + newLine;
        result += CnonceDate + newLine;
        result += NC + newLine;
        result += Algorithm + newLine;

        return result;
    }
}

显然,将所有 header 添加到请求中的顺序很重要。使用 hookbin,我能够检测到,即使我在我的 digestRequest object 上放置了授权 header,它也没有跟进 hookbin。

我将授权 header 添加到设置方法行的正下方,一切正常...

我不知道这会造成问题。我的 GET 方法起作用的原因是因为 'content' 是空的,所以没有添加 header。

var digestRequest = (HttpWebRequest)WebRequest.Create(uri);
digestRequest.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
digestRequest.Headers.Add("Authorization", GetDigestHeader(nUrl));