博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WebApi接口安全认证——HTTP之摘要认证
阅读量:5095 次
发布时间:2019-06-13

本文共 17673 字,大约阅读时间需要 58 分钟。

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

 

一、摘要认证基本流程:

 

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

Html代码  
  1. GET /dir/index.html HTTP/1.0  
  2. Host: localhost  
GET /dir/index.html HTTP/1.0Host: 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"  
HTTP/1.0 401 UnauthorizedServer: HTTPd/0.9Date: Sun, 10 Apr 2005 20:26:47 GMTWWW-Authenticate: Digest realm="testrealm@host.com",                        qop="auth,auth-int",                        nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",                        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)  
response计算过程:HA1=MD5(A1)=MD5(username:realm:password)如果 qop 值为“auth”或未指定,那么 HA2 为HA2=MD5(A2)=MD5(method:digestURI)如果 qop 值为“auth-int”,那么 HA2 为HA2=MD5(A2)=MD5(method:digestURI:MD5(entityBody))如果 qop 值为“auth”或“auth-int”,那么如下计算 response:response=MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)如果 qop 未指定,那么如下计算 response: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"  
GET /dir/index.html HTTP/1.0Host: localhostAuthorization: Digest username="Mufasa",                     realm="testrealm@host.com",                     nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",                     uri="/dir/index.html",                     qop=auth,                     nc=00000001,                     cnonce="0a4f113b",                     response="6629fae49393a05397450978507c4ef1",                     opaque="5ccc069c403ebaf9f0171e9517f40e41"

 

4.服务器响应

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

 

Html代码  
  1. HTTP/1.0 200 OK  
HTTP/1.0 200 OK

 

二、服务端验证

编写一个自定义消息处理器,需要通过System.Net.Http.DelegatingHandler进行派生,并重写SendAsync方法。

 

C#代码  
  1. public class AuthenticationHandler : DelegatingHandler  
  2. {  
  3.     protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)  
  4.     {  
  5.         try  
  6.         {  
  7.             HttpRequestHeaders headers = request.Headers;  
  8.             if (headers.Authorization != null)  
  9.             {  
  10.                 Header header = new Header(request.Headers.Authorization.Parameter, request.Method.Method);  
  11.   
  12.                 if (Nonce.IsValid(header.Nonce, header.NounceCounter))  
  13.                 {  
  14.                     // Just assuming password is same as username for the purpose of illustration  
  15.                     string password = header.UserName;  
  16.   
  17.                     string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, password).ToMD5Hash();  
  18.   
  19.                     string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash();  
  20.   
  21.                     string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",  
  22.                                         ha1, header.Nonce, header.NounceCounter,header.Cnonce, "auth", ha2).ToMD5Hash();  
  23.   
  24.                     if (String.CompareOrdinal(header.Response, computedResponse) == 0)  
  25.                     {  
  26.                         // digest computed matches the value sent by client in the response field.  
  27.                         // Looks like an authentic client! Create a principal.  
  28.                         var claims = new List<Claim>  
  29.                         {  
  30.                                         new Claim(ClaimTypes.Name, header.UserName),  
  31.                                         new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)  
  32.                         };  
  33.   
  34.                         ClaimsPrincipal principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") });  
  35.   
  36.                         Thread.CurrentPrincipal = principal;  
  37.   
  38.                         if (HttpContext.Current != null)  
  39.                             HttpContext.Current.User = principal;  
  40.                     }  
  41.                 }  
  42.             }  
  43.   
  44.             HttpResponseMessage response = await base.SendAsync(request, cancellationToken);  
  45.   
  46.             if (response.StatusCode == HttpStatusCode.Unauthorized)  
  47.             {  
  48.                 response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));  
  49.             }  
  50.   
  51.             return response;  
  52.         }  
  53.         catch (Exception)  
  54.         {  
  55.             var response = request.CreateResponse(HttpStatusCode.Unauthorized);  
  56.             response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));  
  57.   
  58.             return response;  
  59.         }  
  60.     }  
  61. }  
public class AuthenticationHandler : DelegatingHandler{    protected async override Task
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { try { HttpRequestHeaders headers = request.Headers; if (headers.Authorization != null) { Header header = new Header(request.Headers.Authorization.Parameter, request.Method.Method); if (Nonce.IsValid(header.Nonce, header.NounceCounter)) { // Just assuming password is same as username for the purpose of illustration string password = header.UserName; string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, password).ToMD5Hash(); string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash(); string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}", ha1, header.Nonce, header.NounceCounter,header.Cnonce, "auth", ha2).ToMD5Hash(); if (String.CompareOrdinal(header.Response, computedResponse) == 0) { // digest computed matches the value sent by client in the response field. // Looks like an authentic client! Create a principal. var claims = new List
{ new Claim(ClaimTypes.Name, header.UserName), new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password) }; ClaimsPrincipal principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") }); Thread.CurrentPrincipal = principal; if (HttpContext.Current != null) HttpContext.Current.User = principal; } } } HttpResponseMessage response = await base.SendAsync(request, cancellationToken); if (response.StatusCode == HttpStatusCode.Unauthorized) { response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString())); } return response; } catch (Exception) { var response = request.CreateResponse(HttpStatusCode.Unauthorized); response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString())); return response; } }}

 

 Header类

 

C#代码  
  1. public class Header  
  2. {  
  3.     public Header() { }  
  4.   
  5.     public Header(string header, string method)  
  6.     {  
  7.         string keyValuePairs = header.Replace("\"", String.Empty);  
  8.   
  9.         foreach (string keyValuePair in keyValuePairs.Split(','))  
  10.         {  
  11.             int index = keyValuePair.IndexOf("=", System.StringComparison.Ordinal);  
  12.             string key = keyValuePair.Substring(0, index);  
  13.             string value = keyValuePair.Substring(index + 1);  
  14.   
  15.             switch (key)  
  16.             {  
  17.                 case "username": this.UserName = value; break;  
  18.                 case "realm": this.Realm = value; break;  
  19.                 case "nonce": this.Nonce = value; break;  
  20.                 case "uri": this.Uri = value; break;  
  21.                 case "nc": this.NounceCounter = value; break;  
  22.                 case "cnonce": this.Cnonce = value; break;  
  23.                 case "response": this.Response = value; break;  
  24.                 case "method": this.Method = value; break;  
  25.             }  
  26.         }  
  27.   
  28.         if (String.IsNullOrEmpty(this.Method))  
  29.             this.Method = method;  
  30.     }  
  31.   
  32.     public string Cnonce { get; private set; }  
  33.     public string Nonce { get; private set; }  
  34.     public string Realm { get; private set; }  
  35.     public string UserName { get; private set; }  
  36.     public string Uri { get; private set; }  
  37.     public string Response { get; private set; }  
  38.     public string Method { get; private set; }  
  39.     public string NounceCounter { get; private set; }  
  40.   
  41.     // This property is used by the handler to generate a  
  42.     // nonce and get it ready to be packaged in the  
  43.     // WWW-Authenticate header, as part of 401 response  
  44.     public static Header UnauthorizedResponseHeader  
  45.     {  
  46.         get  
  47.         {  
  48.             return new Header()  
  49.             {  
  50.                 Realm = "MyRealm",  
  51.                 Nonce = WebApiDemo.Nonce.Generate()  
  52.             };  
  53.         }  
  54.     }  
  55.   
  56.     public override string ToString()  
  57.     {  
  58.         StringBuilder header = new StringBuilder();  
  59.         header.AppendFormat("realm=\"{0}\"", Realm);  
  60.         header.AppendFormat(",nonce=\"{0}\"", Nonce);  
  61.         header.AppendFormat(",qop=\"{0}\"", "auth");  
  62.         return header.ToString();  
  63.     }  
  64. }  
public class Header{    public Header() { }    public Header(string header, string method)    {        string keyValuePairs = header.Replace("\"", String.Empty);        foreach (string keyValuePair in keyValuePairs.Split(','))        {            int index = keyValuePair.IndexOf("=", System.StringComparison.Ordinal);            string key = keyValuePair.Substring(0, index);            string value = keyValuePair.Substring(index + 1);            switch (key)            {                case "username": this.UserName = value; break;                case "realm": this.Realm = value; break;                case "nonce": this.Nonce = value; break;                case "uri": this.Uri = value; break;                case "nc": this.NounceCounter = value; break;                case "cnonce": this.Cnonce = value; break;                case "response": this.Response = value; break;                case "method": this.Method = value; break;            }        }        if (String.IsNullOrEmpty(this.Method))            this.Method = method;    }    public string Cnonce { get; private set; }    public string Nonce { get; private set; }    public string Realm { get; private set; }    public string UserName { get; private set; }    public string Uri { get; private set; }    public string Response { get; private set; }    public string Method { get; private set; }    public string NounceCounter { get; private set; }    // This property is used by the handler to generate a    // nonce and get it ready to be packaged in the    // WWW-Authenticate header, as part of 401 response    public static Header UnauthorizedResponseHeader    {        get        {            return new Header()            {                Realm = "MyRealm",                Nonce = WebApiDemo.Nonce.Generate()            };        }    }    public override string ToString()    {        StringBuilder header = new StringBuilder();        header.AppendFormat("realm=\"{0}\"", Realm);        header.AppendFormat(",nonce=\"{0}\"", Nonce);        header.AppendFormat(",qop=\"{0}\"", "auth");        return header.ToString();    }}

 nonce类

 

C#代码  
  1. public class Nonce  
  2. {  
  3.     private static ConcurrentDictionary<string, Tuple<int, DateTime>>  
  4.     nonces = new ConcurrentDictionary<string, Tuple<int, DateTime>>();  
  5.   
  6.     public static string Generate()  
  7.     {  
  8.         byte[] bytes = new byte[16];  
  9.   
  10.         using (var rngProvider = new RNGCryptoServiceProvider())  
  11.         {  
  12.             rngProvider.GetBytes(bytes);  
  13.         }  
  14.   
  15.         string nonce = bytes.ToMD5Hash();  
  16.   
  17.         nonces.TryAdd(nonce, new Tuple<int, DateTime>(0, DateTime.Now.AddMinutes(10)));  
  18.   
  19.         return nonce;  
  20.     }  
  21.   
  22.     public static bool IsValid(string nonce, string nonceCount)  
  23.     {  
  24.         Tuple<int, DateTime> cachedNonce = null;  
  25.         //nonces.TryGetValue(nonce, out cachedNonce);  
  26.         nonces.TryRemove(nonce, out cachedNonce);//每个nonce只允许使用一次  
  27.   
  28.         if (cachedNonce != null) // nonce is found  
  29.         {  
  30.             // nonce count is greater than the one in record  
  31.             if (Int32.Parse(nonceCount) > cachedNonce.Item1)  
  32.             {  
  33.                 // nonce has not expired yet  
  34.                 if (cachedNonce.Item2 > DateTime.Now)  
  35.                 {  
  36.                     // update the dictionary to reflect the nonce count just received in this request  
  37.                     //nonces[nonce] = new Tuple<int, DateTime>(Int32.Parse(nonceCount), cachedNonce.Item2);  
  38.   
  39.                     // Every thing looks ok - server nonce is fresh and nonce count seems to be   
  40.                     // incremented. Does not look like replay.  
  41.                     return true;  
  42.                 }  
  43.                      
  44.             }  
  45.         }  
  46.   
  47.         return false;  
  48.     }  
  49. }  
public class Nonce{    private static ConcurrentDictionary
> nonces = new ConcurrentDictionary
>(); public static string Generate() { byte[] bytes = new byte[16]; using (var rngProvider = new RNGCryptoServiceProvider()) { rngProvider.GetBytes(bytes); } string nonce = bytes.ToMD5Hash(); nonces.TryAdd(nonce, new Tuple
(0, DateTime.Now.AddMinutes(10))); return nonce; } public static bool IsValid(string nonce, string nonceCount) { Tuple
cachedNonce = null; //nonces.TryGetValue(nonce, out cachedNonce); nonces.TryRemove(nonce, out cachedNonce);//每个nonce只允许使用一次 if (cachedNonce != null) // nonce is found { // nonce count is greater than the one in record if (Int32.Parse(nonceCount) > cachedNonce.Item1) { // nonce has not expired yet if (cachedNonce.Item2 > DateTime.Now) { // update the dictionary to reflect the nonce count just received in this request //nonces[nonce] = new Tuple
(Int32.Parse(nonceCount), cachedNonce.Item2); // Every thing looks ok - server nonce is fresh and nonce count seems to be // incremented. Does not look like replay. return true; } } } return false; }}

 需要使用摘要验证可在代码里添加Attribute [Authorize],如:

C#代码  
  1. [Authorize]  
  2. public class ProductsController : ApiController  
[Authorize]public class ProductsController : ApiController
 最后Global.asax里需注册下
C#代码  
  1. GlobalConfiguration.Configuration.MessageHandlers.Add(  
  2. new AuthenticationHandler());  
GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthenticationHandler());
 
三、客户端的调用
这里主要说明使用WebClient调用
C#代码  
  1. public static string Request(string sUrl, string sMethod, string sEntity, string sContentType,  
  2.     out string sMessage)  
  3. {  
  4.     try  
  5.     {  
  6.         sMessage = "";  
  7.         using (System.Net.WebClient client = new System.Net.WebClient())  
  8.         {  
  9.             client.Credentials = CreateAuthenticateValue(sUrl);  
  10.             client.Headers = CreateHeader(sContentType);  
  11.   
  12.             Uri url = new Uri(sUrl);  
  13.             byte[] bytes = Encoding.UTF8.GetBytes(sEntity);  
  14.             byte[] buffer;  
  15.             switch (sMethod.ToUpper())  
  16.             {  
  17.                 case "GET":  
  18.                     buffer = client.DownloadData(url);  
  19.                     break;  
  20.                 case "POST":  
  21.                     buffer = client.UploadData(url, "POST", bytes);  
  22.                     break;  
  23.                 default:  
  24.                     buffer = client.UploadData(url, "POST", bytes);  
  25.                     break;  
  26.             }  
  27.   
  28.             return Encoding.UTF8.GetString(buffer);  
  29.         }  
  30.     }  
  31.     catch (WebException ex)  
  32.     {  
  33.         sMessage = ex.Message;  
  34.         var rsp = ex.Response as HttpWebResponse;  
  35.         var httpStatusCode = rsp.StatusCode;  
  36.         var authenticate = rsp.Headers.Get("WWW-Authenticate");  
  37.   
  38.         return "";  
  39.     }  
  40.     catch (Exception ex)  
  41.     {  
  42.         sMessage = ex.Message;  
  43.         return "";  
  44.     }  
  45. }  
public static string Request(string sUrl, string sMethod, string sEntity, string sContentType,    out string sMessage){    try    {        sMessage = "";        using (System.Net.WebClient client = new System.Net.WebClient())        {            client.Credentials = CreateAuthenticateValue(sUrl);            client.Headers = CreateHeader(sContentType);            Uri url = new Uri(sUrl);            byte[] bytes = Encoding.UTF8.GetBytes(sEntity);            byte[] buffer;            switch (sMethod.ToUpper())            {                case "GET":                    buffer = client.DownloadData(url);                    break;                case "POST":                    buffer = client.UploadData(url, "POST", bytes);                    break;                default:                    buffer = client.UploadData(url, "POST", bytes);                    break;            }            return Encoding.UTF8.GetString(buffer);        }    }    catch (WebException ex)    {        sMessage = ex.Message;        var rsp = ex.Response as HttpWebResponse;        var httpStatusCode = rsp.StatusCode;        var authenticate = rsp.Headers.Get("WWW-Authenticate");        return "";    }    catch (Exception ex)    {        sMessage = ex.Message;        return "";    }}
 关键代码,在这里添加用户认证,使用NetworkCredential
C#代码  
  1. private static CredentialCache CreateAuthenticateValue(string sUrl)  
  2. {  
  3.     CredentialCache credentialCache = new CredentialCache();  
  4.     credentialCache.Add(new Uri(sUrl), "Digest", new NetworkCredential("Lime", "Lime"));  
  5.   
  6.     return credentialCache;  
  7. }  
private static CredentialCache CreateAuthenticateValue(string sUrl){    CredentialCache credentialCache = new CredentialCache();    credentialCache.Add(new Uri(sUrl), "Digest", new NetworkCredential("Lime", "Lime"));    return credentialCache;}
 至此整个认证就ok了。

转载于:https://www.cnblogs.com/zxh1919/p/7669997.html

你可能感兴趣的文章
lua转让C++书面DLL达到“热更新”
查看>>
oppo9.0系统机器最完美激活xposed框架的方法
查看>>
SCOPE_IDENTITY和@@IDENTITY[转]
查看>>
关于GSMS的制作方法的记录
查看>>
机器学习之特征工程
查看>>
Asp.Net的Cookie用法以及注意事项
查看>>
jquery 图片懒加载
查看>>
android应用安全——(数据抓包)跟踪监控android数据包
查看>>
MapWindow Gis 组件代码示例:
查看>>
JavaScript实现轮播图
查看>>
实验二
查看>>
用C语言画一个心
查看>>
Jlabel实现内容自动换行
查看>>
001——Spring、Hibernate整合
查看>>
ASP.NET MVC 4 Attribute特性
查看>>
hbase学习(一)hbase简介
查看>>
Object C学习笔记8-字符串NSString之二
查看>>
客户信息管理系统
查看>>
设计模式——代理模式
查看>>
c++ 语言细节
查看>>