如何使用 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));
我在这里完全不知所措。我目前成功地针对实现 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));