Как сделать DIGEST с помощью POST, PUT и PATCH

Я здесь в полной растерянности. В настоящее время я успешно выполняю запрос GET к веб-службе, которая реализует аутентификацию DIGEST MD5-sess. Это прекрасно работает. Я получаю ожидаемый результат, поэтому я полагаю, что мой метод «СТРОЙКА АВТОМАТИЧЕСКОГО ЗАГОЛОВКА» работает по назначению.

Затем я получаю требование также поддерживать POST, PUT и PATCH, но теперь я сталкиваюсь с целой кучей проблем. Первый запрос, очевидно, возвращает 401, и я читаю информацию в заголовке WWW-Authenticate и делаю заголовок аутентификации MD5-sess, учитывая, что теперь это запрос POST, PUT или PATCH.

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

Я сохраняю все то же самое, но также, очевидно, добавляю контент в запрос на POST, PUT и PATCH. Но по какой-то причине я не могу понять, что меня заблокировали в сервисе. Второй запрос возвращает еще один 401 даже.

Есть ли что-то особенное, что нужно сделать для DIGEST Auth, когда метод POST, PUT и PATCH отсутствует?

У меня все запросы настроены в Почтальоне, а у Почтальона нет такой проблемы. Второй вызов в Postman дает ожидаемые результаты. 200 и ожидаемые данные по POST, PUT и PATCH.

Я использую слегка измененную версию DigestAuthFixer.cs, которая также доступна в некоторых других сообщениях здесь, в stackoverflow.

Код ниже в настоящее время жестко закодирован для использования метода 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;
    }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
34
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Таким образом, очевидно, имеет значение, в каком порядке вы добавляете все заголовки в запрос. С помощью hookbin я смог обнаружить, что, несмотря на то, что я поместил заголовок Authorization в свой объектdigestRequest, он не прошел через hookbin.

Я разместил дополнение заголовка авторизации чуть ниже настройки строки метода, и все это работает...

Я понятия не имел, что это может создать проблему. Причина, по которой мой метод GET работает, заключается в том, что «контент» пуст, поэтому заголовки не добавляются.

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

Другие вопросы по теме