Digest摘要认证:基于HttpWebRequest请求的Web API Digest身份认证

摘要访问认证是一种协议规定的Web服务器用来同网页浏览器进行认证信息协商的方法。它在密码发出前,先对其应用哈希函数,这相对于HTTP基本认证发送明文而言,更安全。从技术上讲,摘要认证是使用随机数来阻止进行密码分析的MD5加密哈希函数应用。它使用HTTP协议。

一、摘要认证基本流程:

 

1.客户端请求 (无认证)

Html代码 

  1. GET /dir/index.html HTTP/1.0  
  2. Host: localhost  

 

2.服务器响应

服务端返回401未验证的状态,并且返回WWW-Authenticate信息,包含了验证方式Digest,realm,qop,nonce,opaque的值。其中:

Digest:认证方式;

realm:领域,领域参数是强制的,在所有的访问中都必须有,它的目的是鉴别SIP消息中的机密,在SIP实际应用中,它通常设置为SIP代理服务器所负责的域名;

qop:保护的质量,这个参数规定服务器支持哪种保护方案,客户端可以从列表中选择一个。值 “auth”表示只进行身份查验, “auth-int”表示进行查验外,还有一些完整性保护。需要看更详细的描述,请参阅RFC2617;

nonce:为一串随机值,在下面的请求中会一直使用到,当过了存活期后服务端将刷新生成一个新的nonce值;

opaque:一个不透明的(不让外人知道其意义)数据字符串,在访问中发送给用户。

 

Html代码 

  1. HTTP/1.0 401 Unauthorized  
  2. Server: HTTPd/0.9  
  3. Date: Sun, 10 Apr 2005 20:26:47 GMT  
  4. WWW-Authenticate: Digest realm="testrealm@host.com",  
  5. qop="auth,auth-int",  
  6. nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",  
  7. opaque="5ccc069c403ebaf9f0171e9517f40e41"  

 

3.客户端请求  (用户名 "Mufasa", 密码 "Circle Of Life")

客户端接受到请求返回后,进行HASH运算,返回Authorization参数

其中:realm,nonce,qop由服务器产生;

uri:客户端想要访问的URI;

nc:“现时”计数器,这是一个16进制的数值,即客户端发送出请求的数量(包括当前这个请求),这些请求都使用了当前请求中这个“现时”值。例如,对一个给定的“现时”值,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的,是让服务器保持这个计数器的一个副本,以便检测重复的请求。如果这个相同的值看到了两次,则这个请求是重复的;

cnonce:这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护;

response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令。

Html代码

  1. <strong>response计算过程:</strong>  
  2. HA1=MD5(A1)=MD5(username:realm:password)  
  3. 如果 qop 值为“auth”或未指定,那么 HA2 为  
  4. HA2=MD5(A2)=MD5(method:digestURI)  
  5. 如果 qop 值为“auth-int”,那么 HA2 为  
  6. HA2=MD5(A2)=MD5(method:digestURI:MD5(entityBody))  
  7. 如果 qop 值为“auth”或“auth-int”,那么如下计算 response:  
  8. response=MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)  
  9. 如果 qop 未指定,那么如下计算 response:  
  10. response=MD5(HA1:nonce:HA2)  

 请求头:

Html代码 

  1. GET /dir/index.html HTTP/1.0  
  2. Host: localhost  
  3. Authorization: Digest username="Mufasa",  
  4. realm="testrealm@host.com",  
  5. nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",  
  6. uri="/dir/index.html",  
  7. qop=auth,  
  8. nc=00000001,  
  9. cnonce="0a4f113b",  
  10. response="6629fae49393a05397450978507c4ef1",  
  11. opaque="5ccc069c403ebaf9f0171e9517f40e41"  

 

4.服务器响应

当服务器接收到摘要响应,也要重新计算响应中各参数的值,并利用客户端提供的参数值,和服务器上存储的口令,进行比对。如果计算结果与收到的客户响应值是相同的,则客户已证明它知道口令,因而客户的身份验证通过。

 

Html代码

  1. HTTP/1.0 200 OK  

//1、身份验证类封装

public class DigestHttpWebRequest
{
  private string _user;
  private string _password;
  private string _realm;
  private string _nonce;
  private string _qop;
  private string _cnonce;
  private string _opaque;
  private DateTime _cnonceDate;
  private int _nc;
  private string _requestMethod = WebRequestMethods.Http.Get;
  private string _contentType;
  private byte[] _postData;
  public DigestHttpWebRequest(string user, string password)
  {
    _user = user;
    _password = password;
  }
  public DigestHttpWebRequest(string user, string password, string realm)
  {
    _user = user;
    _password = password;
    _realm = realm;
  }
  public string Method
  {
    get
    {
      return _requestMethod;
    }
    set
    {
    _requestMethod = value;
    }
  }
public string ContentType
{
get
{
return _contentType;
}
set
{
_contentType = value;
}
}
public byte[] PostData
{
get
{
return _postData;
}
set
{
_postData = value;
}
}
public HttpWebResponse GetResponse(Uri uri)
{
HttpWebResponse response = null;
int infiniteLoopCounter = 0;
int maxNumberAttempts = 2;
while((response == null || response.StatusCode != HttpStatusCode.Accepted) && infiniteLoopCounter < maxNumberAttempts)
{
try
{
var request = CreateHttpWebRequestObject(uri);
// If we've got a recent Auth header, re-use it!
if(!string.IsNullOrEmpty(_cnonce) && DateTime.Now.Subtract(_cnonceDate).TotalHours < 1.0)
{
request.Headers.Add("Authorization", ComputeDigestHeader(uri));
}
try
{
response = (HttpWebResponse) request.GetResponse();
}
catch(WebException webException)
{
// Try to fix a 401 exception by adding a Authorization header
if(webException.Response != null && ((HttpWebResponse) webException.Response).StatusCode == HttpStatusCode.Unauthorized)
{
var wwwAuthenticateHeader = webException.Response.Headers["WWW-Authenticate"];
_realm = GetDigestHeaderAttribute("realm", wwwAuthenticateHeader);
_nonce = GetDigestHeaderAttribute("nonce", wwwAuthenticateHeader);
_qop = GetDigestHeaderAttribute("qop", wwwAuthenticateHeader);
_opaque = GetDigestHeaderAttribute("opaque", wwwAuthenticateHeader);
_nc = 0;
_cnonce = new Random().Next(123400, 9999999).ToString();
_cnonceDate = DateTime.Now;
request = CreateHttpWebRequestObject(uri, true);
infiniteLoopCounter++;
response = (HttpWebResponse) request.GetResponse();
}
else
{
throw webException;
}
}
switch(response.StatusCode)
{
case HttpStatusCode.OK:
case HttpStatusCode.Accepted:
return response;
case HttpStatusCode.Redirect:
case HttpStatusCode.Moved:
uri = new Uri(response.Headers["Location"]);
// We decrement the loop counter, as there might be a variable number of redirections which we should follow
infiniteLoopCounter--;
break;
}
}
catch(WebException ex)
{
throw ex;
}
}
throw new Exception("Error: Either authentication failed, authorization failed or the resource doesn't exist");
}
private HttpWebRequest CreateHttpWebRequestObject(Uri uri, bool addAuthenticationHeader)
{
var request = (HttpWebRequest) WebRequest.Create(uri);
request.AllowAutoRedirect = true;
request.PreAuthenticate = true;
request.Method = this.Method;
///Only for test reason. The Ip is the one for fiddler
//request.Proxy = new WebProxy("127.0.0.1:8888", true);
if(!String.IsNullOrEmpty(this.ContentType))
{
request.ContentType = this.ContentType;
}
if(addAuthenticationHeader)
{
request.Headers.Add("Authorization", ComputeDigestHeader(uri));
}
if(this.PostData != null && this.PostData.Length > 0)
{
request.ContentLength = this.PostData.Length;
Stream postDataStream = request.GetRequestStream(); //open connection
postDataStream.Write(this.PostData, 0, this.PostData.Length); // Send the data.
postDataStream.Close();
}
else if(this.Method == WebRequestMethods.Http.Post && (this.PostData == null || this.PostData.Length == 0))
{
request.ContentLength = 0;
}
return request;
}
private HttpWebRequest CreateHttpWebRequestObject(Uri uri)
{
return CreateHttpWebRequestObject(uri, false);
}
private string ComputeDigestHeader(Uri uri)
{
_nc = _nc + 1;
string ha1, ha2;
ha1 = ComputeMd5Hash(string.Format("{0}:{1}:{2}", _user, _realm, _password));
ha2 = ComputeMd5Hash(string.Format("{0}:{1}", this.Method, uri.PathAndQuery));
var digestResponse = ComputeMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, _nonce, _nc, _cnonce, _qop, ha2));
return string.Format("Digest username=\"{0}\",realm=\"{1}\",nonce=\"{2}\",uri=\"{3}\"," + "cnonce=\"{7}\",nc={6:00000000},qop={5},response=\"{4}\",opaque=\"{8}\"", _user, _realm, _nonce, uri.PathAndQuery, digestResponse, _qop, _nc, _cnonce, _opaque);
throw new Exception("The digest header could not be generated");
}
private string GetDigestHeaderAttribute(string attributeName, string digestAuthHeader)
{
var regHeader = new Regex(string.Format(@"{0}="
"([^"
"]*)"
"", attributeName));
var matchHeader = regHeader.Match(digestAuthHeader);
if(matchHeader.Success) return matchHeader.Groups[1].Value;
throw new ApplicationException(string.Format("Header {0} not found", attributeName));
}
private string ComputeMd5Hash(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();
}
}

 

//2.0 调用类封装
public class DigestProxy
{
public static byte[] GetData(string url, string userName, string password)
{
try
{
Uri uri = new Uri(url);
DigestHttpWebRequest req = new DigestHttpWebRequest(userName, password);
req.Method = WebRequestMethods.Http.Post;
req.ContentType = "application/json; charset=UTF-8";
object para = new
{
RegisterObject = new
{
UserID = userName
}
};
req.PostData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(para));
using(HttpWebResponse webResponse = req.GetResponse(uri))
using(Stream responseStream = webResponse.GetResponseStream())
{
using(var memoryStream = new MemoryStream())
{
responseStream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
catch(WebException caught)
{
throw new WebException(string.Format("Exception in WebServiceCall: {0}", caught.Message));
}
catch(Exception caught)
{
throw new Exception(string.Format("Exception in WebServiceCall: {0}", caught.Message));
}
}


public static string GetData(string url, string userName, string password, Encoding encoding)
{
  byte[] data = DigestProxy.GetData(url, userName, password);
  return encoding.GetString(data);
}


}

 

//3.0 测试调用部分代码

string token = "v5hFOcpnQMCAWFnk";

string host = "";
string user = "";
string password = "";

if (string.IsNullOrEmpty(token))
{
  string login = "";

   string mess = DigestProxy.GetData(login, user, password, Encoding.UTF8);


JObject jo = (JObject)JsonConvert.DeserializeObject(mess);

if (jo["StatusCode"].ToString() == "0")
{
token = jo["Status"].ToString();
}
}

 

 

posted @   Sikkeung  阅读(1292)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示