WebApi接口安全認證

    摘要訪問認證是一種協議規定的Web服務器用來同網頁瀏覽器進行認證信息協商的方法。它在密碼發出前,先對其應用哈希函數,這相對於HTTP基本認證發送明文而言,更安全。從技術上講,摘要認證是使用隨機數來阻止進行密碼分析的MD5加密哈希函數應用。它使用HTTP協議。html

 

1、摘要認證基本流程:瀏覽器

 

1.客戶端請求 (無認證)安全

Html代碼   收藏代碼
  1. GET /dir/index.html HTTP/1.0  
  2. Host: localhost  

 

2.服務器響應服務器

服務端返回401未驗證的狀態,而且返回WWW-Authenticate信息,包含了驗證方式Digest,realm,qop,nonce,opaque的值。其中:async

Digest:認證方式;ide

realm:領域,領域參數是強制的,在全部的盤問中都必須有,它的目的是鑑別SIP消息中的機密,在SIP實際應用中,它一般設置爲SIP代理服務器所負責的域名;函數

qop:保護的質量,這個參數規定服務器支持哪一種保護方案,客戶端能夠從列表中選擇一個。值 「auth」表示只進行身份查驗, 「auth-int」表示進行查驗外,還有一些完整性保護。須要看更詳細的描述,請參閱RFC2617;ui

nonce:爲一串隨機值,在下面的請求中會一直使用到,當過了存活期後服務端將刷新生成一個新的nonce值;this

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  

 

2、服務端驗證

編寫一個自定義消息處理器,須要經過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. }  
 

 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. }  
 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. }  
 須要使用摘要驗證可在代碼裏添加Attribute [Authorize],如:
C#代碼   收藏代碼
  1. [Authorize]  
  2. public class ProductsController : ApiController  
 最後Global.asax裏需註冊下
C#代碼   收藏代碼
  1. GlobalConfiguration.Configuration.MessageHandlers.Add(  
  2. new AuthenticationHandler());  
 
3、客戶端的調用
這裏主要說明使用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. }  
 關鍵代碼,在這裏添加用戶認證,使用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. }  
相關文章
相關標籤/搜索