• [转]ASP.NET Web API(三):安全验证之使用摘要认证(digest authentication)


    本文转自:http://www.cnblogs.com/parry/p/ASPNET_MVC_Web_API_digest_authentication.html

    前一篇文章中,主要讨论了使用HTTP基本认证的方法,因为HTTP基本认证的方式决定了它在安全性方面存在很大的问题,所以接下来看看另一种验证的方式:digest authentication,即摘要认证。

    系列文章列表

    ASP.NET Web API(一):使用初探,GET和POST数据 ASP.NET Web API(二):安全验证之使用HTTP基本认证 ASP.NET Web API(三):安全验证之使用摘要认证(digest authentication)

    摘要认证原理

    在基本认证的方式中,主要的安全问题来自于用户信息的明文传输,而在摘要认证中,主要通过一些手段避免了此问题,大大增加了安全性。

    下图为摘要验证的验证原理流程图。

    下面大致看一下这部分的验证流程:

    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比较的步骤避免掉了基本验证中的安全性问题。

    需要注意的是,如果需要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]属性标签即可。

    摘要验证的优缺点

    摘要验证很好地解决了使用基本验证所担心的安全性问题。

    但是永远没有绝对的安全,当用户使用字典进行穷举破解时,还是会存在一些被破解的隐患。

    源码下载

    编辑器里怎么找不到上传文件的地方了?我上传到了百度网盘里。

    源代码下载

    作者:Parry            出处:http://www.cnblogs.com/parry/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 

     
    分类: 00.ASP.NET
  • 相关阅读:
    uni-app引入自己ui写的icon
    2020.07.17
    读uni-app
    图片上传,配置php以及vue简单实现
    vue子给父组件传值
    vue父子传值以及监听这个值,把值拿到子组件使用不报警告
    vue局部跳转,实现一个类似选项卡tab功能
    vuex的一个demo,数据拿到界面上
    vue-day06&day07----路由、路由常用的配置项、vue路由的内置组件、vue路由跳转的方式、路由传值、路由解耦(路由传值的一种方式)、编程式导航、嵌套路由children、路由元信息meta、路由生命周期(路由守卫)、路由懒加载、vue/cli、webpack配置alias、vue/cli中配置alias、vue-router底层原理、单页面开发的缺点、路由报错
    vue-day06----过渡&动画(transition)、axios、axios以post方式请求数据、axios常用的配置项、axios拦截器、qs处理数据、vue中async和await的使用
  • 原文地址:https://www.cnblogs.com/freeliver54/p/5577017.html
Copyright © 2020-2023  润新知