• 【ASP.NET Web API2】Digest认证


    Digest摘要认证

         对于Basic认证方案来说,被传输的认证凭证仅仅采用Base64编码,所以包含密码的认证凭证基本上可以视为明文传输。Digest认证在此基础上进行了改善,在一定程度上提高了安全系数。

         在了解Digest认证的工作原理之前,我们看看通过浏览器调用一个Digest认证的Web API会涉及怎样的消息交换

         采用浏览器作为Web API调用的客户端,与Basic认证方式一样,浏览器会针对Web API 的第一次调用  会自动弹出登陆对话框

    正确输入账号密码(Web API 配置文件中定义)后,输出结果会呈现在浏览器中。利用Fiddler我们会发现整个调用涉及两次消息交换

    GET http://localhost:4003/api/values HTTP/1.1
    Host: localhost:4003
    Connection: keep-alive
    Cache-Control: max-age=0
    Authorization: Digest username="", realm="UZ.COM", nonce="1b4d156b1af16f4ea72a040396d27f86", uri="/api/values", response="07e5a5012586dbffbad8040a923fb11a", qop=auth, nc=00000001, cnonce="4aee7270082bcd10"
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36
    Accept-Encoding: gzip, deflate, sdch
    Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
    
    
    HTTP/1.1 401 Unauthorized
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: application/xml; charset=utf-8
    Expires: -1
    Server: Microsoft-IIS/8.0
    WWW-Authenticate: Digest realm="UZ.COM",nonce="4c4c15480802fe3bccb45605f1f4126d",qop="auth"
    X-AspNet-Version: 4.0.30319
    X-SourceFiles: =?UTF-8?B?RTpcVGVzdFxEaWdlc3REZW1vXERpZ2VzdERlbW9cYXBpXHZhbHVlcw==?=
    X-Powered-By: ASP.NET
    Date: Fri, 12 Jun 2015 08:16:02 GMT
    Content-Length: 81
    
    <Error><Message>Authorization has been denied for this request.</Message></Error>
    

      

    GET http://localhost:4003/api/values HTTP/1.1
    Host: localhost:4003
    Connection: keep-alive
    Cache-Control: max-age=0
    Authorization: Digest username="loding", realm="UZ.COM", nonce="4c4c15480802fe3bccb45605f1f4126d", uri="/api/values", response="9495b8ef2b5b1d6dd3001753293cca57", qop=auth, nc=00000001, cnonce="2dfe2e5c0b8a4000"
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36
    Accept-Encoding: gzip, deflate, sdch
    Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
    
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: application/xml; charset=utf-8
    Expires: -1
    Server: Microsoft-IIS/8.0
    X-AspNet-Version: 4.0.30319
    X-SourceFiles: =?UTF-8?B?RTpcVGVzdFxEaWdlc3REZW1vXERpZ2VzdERlbW9cYXBpXHZhbHVlcw==?=
    X-Powered-By: ASP.NET
    Date: Fri, 12 Jun 2015 08:17:21 GMT
    Content-Length: 195
    
    <ArrayOfstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays"><string>value1</string><string>value2</string></ArrayOfstring>
    

      

    验证流程:

    1,客户端以匿名的方式发送一个请求

    2,服务器返回状态为"401 Unauthorized",这个响应含有"WWW-Authenticate"报头

    •    Digest:表示当前的认证方式。
    •    realm:领域
    •    nonce:服务器生成的具有唯一性的字符串
    •    qop(Quanlity of Protection):IIS采用qop通知客户端采用的消息保护等级。可选的qop包括"auth"和"auth-int"。

                                          前者表示仅限于基本的认证,后者则表示除了对客户端实施认证外,还需要确保传输内容的一致性。

    3,客户端接收到返回的401状态后,会自动弹出一个登陆对话框

    4,得到用户输入的用户名和密码,浏览器根据密码和服务端生成的nonce采用一种算法生成一个Digest,这个Digest会作为认证凭证放            到"Authorization"报头response中,随后向服务器发送请求

       其他信息:

    • username:代表客户端用户名
    • nonce:就是服务器生成返回的那个nonce
    • cnonce:这是客户端自行生成的nonce,它可以对请求内容进行签名以确保内容没有被篡改,同时也可以帮助客户端对服务器实施认证
    • nc(nonce count):表示客户端针对同一个nonce发送请求的数量,随着请求的发送而不断递增。

                             IIS可以通过nc代表的数字来防止“重发攻击”。

    5,服务器接收到请求后进行nc合法性验证等,然后确定客户端提供密码的正确性,完成对客户端的认证

     

    Digest认证实现(源码下载)

    https://coding.net/u/Allen_/p/ASP.NET-Web-API-2---Digest/git

    验证实现的核心方法:

    public class AuthenticationHandler : DelegatingHandler
        {
            protected async override Task<HttpResponseMessage> 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))
                        {
                            string uzkey = ConfigurationManager.AppSettings["UZKey"];
                            string uzsecret = ConfigurationManager.AppSettings["UZSecret"];
    
                            string ha1 = HashHelper.GetMD5(String.Format("{0}:{1}:{2}", uzkey, header.Realm, uzsecret));
    
                            string ha2 = HashHelper.GetMD5(String.Format("{0}:{1}", header.Method, header.Uri));
    
                            string computedResponse = HashHelper.GetMD5(String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
                                                ha1, header.Nonce, header.NounceCounter, header.Cnonce, "auth", ha2));
    
                            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)
                            };
    
                                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;
                }
            }
        }
    

    使用Digest认证还需要在全局配置文件中注册并在Controller中加[Authorize]属性标签

    使用WebClient调用Web API

    private  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);
                        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 "";
                }
            }
    
    private static CredentialCache CreateAuthenticateValue(string sUrl)  
    {  
        CredentialCache credentialCache = new CredentialCache();  
        credentialCache.Add(new Uri(sUrl), "Digest", new NetworkCredential("用户名", "密码"));  
      
        return credentialCache;  
    }  
    

      

  • 相关阅读:
    MySQL插入数据慢解决办法
    java的 System.currentTimeMillis() 与时区
    单元测试断言系统 类似于 生活中如何说话
    IDEA、Eclipse 默认常用快捷键对比总结,visual studio快捷键总结
    使用过的正则匹配的例子
    filters的this问题
    vuex的使用
    offset/scroll/clien系列知识点
    promise对象的理解
    语义化标签的理解
  • 原文地址:https://www.cnblogs.com/Sunlimi/p/webapi-digest.html
Copyright © 2020-2023  润新知