ASP.NET Web API(三):安全驗證之使用摘要認證(digest authentication)

在前一篇文章中,主要討論了使用HTTP基本認證的方法,由於HTTP基本認證的方式決定了它在安全性方面存在很大的問題,因此接下來看看另外一種驗證的方式:digest authentication,即摘要認證。api

摘要認證原理

在基本認證的方式中,主要的安全問題來自於用戶信息的明文傳輸,而在摘要認證中,主要經過一些手段避免了此問題,大大增長了安全性。安全

下圖爲摘要驗證的驗證原理流程圖。async

下面大體看一下這部分的驗證流程:ide

  1. 客戶端請求 /api/employees;
  2. 服務端返回401未驗證的狀態,而且在返回的信息中包含了驗證方式Digest,realm的值,QOP(quality of protection)只設置成auth,nonce爲一串隨機值,在下面的請求中會一直使用到,當過了存活期後服務端將刷新生成一個新的nonce值;
  3. 客戶端接受到請求返回後,將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值。
  4. 服務端收到請求後將驗證nonce是否過時,若是過時,那麼直接返回401,即第二步的狀態。若是沒有過時,那麼比較nc值,若是比前一次nc值小或者前一次根本沒有存儲的nc值,那麼也將直接返回401狀態。若是前面的驗證都經過,那麼服務端也將按照步驟3中計算最終HASH值的步驟計算出HASH值與客戶端的進行比較,而後比較客戶端提交過來的HASH值與服務端計算出來的HASH進行比較,不匹配返回401,匹配獲取請求的數據並返回狀態200。

摘要驗證主要就是經過上面的HASH比較的步驟避免掉了基本驗證中的安全性問題。this

須要注意的是,若是須要IIS支持摘要驗證,須要把IIS摘要驗證的特性勾上。spa

摘要驗證的實現

在理解了摘要驗證的原理以後,只須要用代碼實現便可。code

判斷nonce是否過時的方法。orm

public static bool IsValid(string nonce, string nonceCount)
        {
            Tuple<int, DateTime> cachedNonce = null;
            nonces.TryGetValue(nonce, out cachedNonce);

            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<int, DateTime>(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;
        }

判斷nonce是否過時的代碼
View Code

下面爲摘要驗證明現的核心方法server

namespace DigestAuthentication
{
    public class AuthenticationHandler : DelegatingHandler
    {
        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                var 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<Claim>
                            {
                                            new Claim(ClaimTypes.Name, header.UserName),
                                            new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)
                            };

                            var principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") });

                            Thread.CurrentPrincipal = principal;

                            if (HttpContext.Current != null)
                                HttpContext.Current.User = principal;
                        }
                    }
                }

                var 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;
            }
        }
    }

}

摘要驗證明現的核心方法
View Code

實現完成後,使用摘要驗證只須要在對應的方法加上[Authorize]屬性標籤便可。blog

摘要驗證的優缺點

摘要驗證很好地解決了使用基本驗證所擔憂的安全性問題。

可是永遠沒有絕對的安全,當用戶使用字典進行窮舉破解時,仍是會存在一些被破解的隱患。

源碼下載

源代碼下載

相關文章
相關標籤/搜索