Я здесь в полной растерянности. В настоящее время я успешно выполняю запрос 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;
}
}
Таким образом, очевидно, имеет значение, в каком порядке вы добавляете все заголовки в запрос. С помощью hookbin я смог обнаружить, что, несмотря на то, что я поместил заголовок Authorization в свой объектdigestRequest, он не прошел через hookbin.
Я разместил дополнение заголовка авторизации чуть ниже настройки строки метода, и все это работает...
Я понятия не имел, что это может создать проблему. Причина, по которой мой метод GET работает, заключается в том, что «контент» пуст, поэтому заголовки не добавляются.
var digestRequest = (HttpWebRequest)WebRequest.Create(uri);
digestRequest.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
digestRequest.Headers.Add("Authorization", GetDigestHeader(nUrl));