在前一篇文章中,主要討論了使用HTTP基本認證的方法,因為HTTP基本認證的方式決定了它在安全性方面存在很大的問題,所以接下來看看另一種驗證的方式:digest authentication,即摘要認證。
系列文章列表
ASP.NET Web API(一):使用初探,GET和POST數據
ASP.NET Web API(二):安全驗證之使用HTTP基本認證
ASP.NET Web API(三):安全驗證之使用摘要認證(digest authentication)
摘要認證原理
在基本認證的方式中,主要的安全問題來自於用戶信息的明文傳輸,而在摘要認證中,主要通過一些手段避免了此問題,大大增加了安全性。
下圖為摘要驗證的驗證原理流程圖。
下面大致看一下這部分的驗證流程:
- 客戶端請求 /api/employees;
- 服務端返回401未驗證的狀態,並且在返回的信息中包含了驗證方式Digest,realm的值,QOP(quality of protection)只設置成auth,nonce為一串隨機值,在下面的請求中會一直使用到,當過了存活期后服務端將刷新生成一個新的nonce值;
- 客戶端接受到請求返回后,將username:realm:password進行HASH運算,假設運算后的值為HA1。又將請求的路徑/api/employees進行HASH運算,假設運算后的值為HA2。再將HA1:nonce:nc:cnonce:qop:HA2進行HASH運算,得到的值放在response中。這里的cnonce為客戶端生成的nonce值,而nc用於統計,假設開始時為00000001,下次請求后就變成了00000002,不一定每次都加1,但是后面請求中的nc值肯定大於前一次請求中的nc值。
- 服務端收到請求后將驗證nonce是否過期,如果過期,那么直接返回401,即第二步的狀態。如果沒有過期,那么比較nc值,如果比前一次nc值小或者前一次根本沒有存儲的nc值,那么也將直接返回401狀態。如果前面的驗證都通過,那么服務端也將按照步驟3中計算最終HASH值的步驟計算出HASH值與客戶端的進行比較,然后比較客戶端提交過來的HASH值與服務端計算出來的HASH進行比較,不匹配返回401,匹配獲取請求的數據並返回狀態200。
摘要驗證主要就是通過上面的HASH比較的步驟避免掉了基本驗證中的安全性問題。
需要注意的是,如果需要IIS支持摘要驗證,需要把IIS摘要驗證的特性勾上。
摘要驗證的實現
在理解了摘要驗證的原理之后,只需要用代碼實現即可。
判斷nonce是否過期的方法。

1 public static bool IsValid(string nonce, string nonceCount) 2 { 3 Tuple<int, DateTime> cachedNonce = null; 4 nonces.TryGetValue(nonce, out cachedNonce); 5 6 if (cachedNonce != null) // nonce is found 7 { 8 // nonce count is greater than the one in record 9 if (Int32.Parse(nonceCount) > cachedNonce.Item1) 10 { 11 // nonce has not expired yet 12 if (cachedNonce.Item2 > DateTime.Now) 13 { 14 // update the dictionary to reflect the nonce count just received in this request 15 nonces[nonce] = new Tuple<int, DateTime>(Int32.Parse(nonceCount), 16 cachedNonce.Item2); 17 18 // Every thing looks ok - server nonce is fresh and nonce count seems to be 19 // incremented. Does not look like replay. 20 return true; 21 } 22 } 23 } 24 25 return false; 26 }
下面為摘要驗證實現的核心方法

1 namespace DigestAuthentication 2 { 3 public class AuthenticationHandler : DelegatingHandler 4 { 5 protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 6 { 7 try 8 { 9 var headers = request.Headers; 10 if (headers.Authorization != null) 11 { 12 Header header = new Header(request.Headers.Authorization.Parameter, 13 request.Method.Method); 14 15 if (Nonce.IsValid(header.Nonce, header.NounceCounter)) 16 { 17 // Just assuming password is same as username for the purpose of illustration 18 string password = header.UserName; 19 20 string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, 21 password).ToMD5Hash(); 22 23 string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash(); 24 25 string computedResponse = String 26 .Format("{0}:{1}:{2}:{3}:{4}:{5}", 27 ha1, header.Nonce, header.NounceCounter, 28 header.Cnonce, "auth", ha2).ToMD5Hash(); 29 30 if (String.CompareOrdinal(header.Response, computedResponse) == 0) 31 { 32 // digest computed matches the value sent by client in the response field. 33 // Looks like an authentic client! Create a principal. 34 var claims = new List<Claim> 35 { 36 new Claim(ClaimTypes.Name, header.UserName), 37 new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password) 38 }; 39 40 var principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") }); 41 42 Thread.CurrentPrincipal = principal; 43 44 if (HttpContext.Current != null) 45 HttpContext.Current.User = principal; 46 } 47 } 48 } 49 50 var response = await base.SendAsync(request, cancellationToken); 51 52 if (response.StatusCode == HttpStatusCode.Unauthorized) 53 { 54 response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", 55 Header.UnauthorizedResponseHeader.ToString())); 56 } 57 58 return response; 59 } 60 catch (Exception) 61 { 62 var response = request.CreateResponse(HttpStatusCode.Unauthorized); 63 response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest", 64 Header.UnauthorizedResponseHeader.ToString())); 65 66 return response; 67 } 68 } 69 } 70 71 }
實現完成后,使用摘要驗證只需要在對應的方法加上[Authorize]屬性標簽即可。
摘要驗證的優缺點
摘要驗證很好地解決了使用基本驗證所擔心的安全性問題。
但是永遠沒有絕對的安全,當用戶使用字典進行窮舉破解時,還是會存在一些被破解的隱患。
源碼下載
編輯器里怎么找不到上傳文件的地方了?我上傳到了百度網盤里。