C# 摘要认证(digest authentication) IETF RFC 2617

背景 :

最近在对接一个公安局数据接口相关的这块业务,基于HTTP  RESTFUL的接口API,请求时需要做用户认证。厂家只给提供了JAVA的demo。由于业务比较分散需要用C#来进行业务交互。

解决过程

首先肯定是各大地方爬文,主要有参考这几篇文章:也感谢各位的分享,给我解决问题上提供了莫大的帮助。

https://www.cnblogs.com/lanxiaoke/p/6357501.html

https://blog.csdn.net/t1269747417/article/details/86038128

参照以上文章 找出几个关键参数

Digest:认证方式;

realm:领域(域名),领域参数是强制的,在所有的盘问中都必须有,它的目的是鉴别SIP消息中的机密,在SIP实际应用中,它通常设置为SIP代理服务器所负责的域名;

qop:保护的质量,这个参数规定服务器支持哪种保护方案,客户端可以从列表中选择一个。值 “auth”表示只进行身份查验, “auth-int”表示进行查验外,还有一些完整性保护。需要看更详细的描述,请参阅RFC2617;

nonce:为一串随机值,在下面的请求中会一直使用到,当过了存活期后服务端将刷新生成一个新的nonce值;

opaque:一个不透明的(不让外人知道其意义)数据字符串,在盘问中发送给用户。

uri:客户端想要访问的URI;

nc:“现时”计数器,这是一个16进制的数值,即客户端发送出请求的数量(包括当前这个请求),这些请求都使用了当前请求中这个“现时”值。例如,对一个给定的“现时”值,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的,是让服务器保持这个计数器的一个副本,以便检测重复的请求。如果这个相同的值看到了两次,则这个请求是重复的;

cnonce:这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护;

response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令。

开始贴代码

 

        /// 
        /// Post 请求(摘要认证(digest authentication))
        /// 
        /// 请求地址
        /// 请求数据
        /// 接口的路由地址
        /// 用户名
        /// 密码
        /// 
        public static string PostResponse(string url,string urlroute, string postData, string username, string password)
        {
            string realm = string.Empty;
            string qop = string.Empty;
            string nonce = string.Empty;
            string opaque = string.Empty;
            string nc = string.Empty;
            string cnonce = string.Empty;

            //请求资源 由服务器返回 数据提交后,
            //服务端检查Headers中的Authorization信息,null值就返回401,提示需要认证,认证格式为Digest,同时返回的还有realm、nonce、qop这几个参数值
            //1、realm的值可以随意;nonce为随机数,一般是GUID格式的字符串,需要后台返回;qop的之分布有三种:没有定义(即空值)、auth、auth-int
            // 第一返回401
            HttpContent httpContent = new StringContent(postData);
            httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            HttpClient httpClient = new HttpClient();
            HttpResponseMessage response = httpClient.PostAsync(url, httpContent).Result;
            if (response.StatusCode == HttpStatusCode.Unauthorized)
            {
                //response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Degist",response.Headers.ToString()));
                Dictionary dic = new Dictionary();

                foreach (var item in response.Headers.WwwAuthenticate)
                {
                    if (item.Scheme=="DIGEST")
                    {
                        dic.Add(item.Scheme, item.Parameter);  
                    }
                }
                foreach (string keyValuePair in dic["DIGEST"].Split(','))
                {
                    int index = keyValuePair.IndexOf("=", System.StringComparison.Ordinal);
                    string key = keyValuePair.Substring(0, index);
                    string value = keyValuePair.Substring(index + 2,keyValuePair.Length-index-3);
                    switch (key)
                    {
                        case "realm": realm = value; break;
                        case "nonce": nonce = value; break;
                        case "qop": qop = value; break;
                        case "opaque": opaque = value; break;
                    }
                }
                 //nc、cnonce是客户端自动生成的值
                 nc= DateTime.Now.ToString("yyMMddHHmmss");
                 cnonce = Guid.NewGuid().ToString();
            }
            string responseString = computeDigestResponse(username, password, realm, "POST", nonce, urlroute, nc, cnonce, qop);
            //注意各值的逗号后面一定要有空格隔开
            StringBuilder sb = new StringBuilder ();
            sb.Append("Digest  username=\""+username+"\", realm=\""+realm+"\", nonce=\""+nonce+"\", uri=\""+urlroute+"\", ");
            sb.Append("qop=\"" + qop + "\", nc=\"" + nc + "\", cnonce=\"" + cnonce + "\", response=\"" + responseString + "\", opaque=\"" + opaque + "\"");
            string headString = sb.ToString();

            //https 访问需要加这块的内容
            ServicePointManager.ServerCertificateValidationCallback += CheckValidationResult;
            var handler = new HttpClientHandler()
            {
                //启用压缩
                AutomaticDecompression = DecompressionMethods.GZip,

            };
            using (var http = new HttpClient(handler))
            {
                //关键部分代码
                http.DefaultRequestHeaders.Add("Authorization", headString);

                var httpContent2 = new StringContent(postData);
                httpContent2.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                var response2 = http.PostAsync(url, httpContent2).Result;
                if (response2.IsSuccessStatusCode)
                {
                    try
                    {
                        var x = response2.Content.ReadAsStringAsync().Result;
                        return x;
                    }
                    catch (Exception)
                    {
                        return null;
                    }
                }
            }
            return null;
        }
        /// 
        /// MD5 32位加密
        /// 
        /// 
        /// 
        private static string md5(string str)
        {
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(str);
            bytes = md5.ComputeHash(bytes);
            md5.Clear();

            string ret = "";
            for (int i = 0; i < bytes.Length; i++)
            {
                ret += Convert.ToString(bytes[i], 16).PadLeft(2, '0');//补0
            }
            return ret.PadLeft(32, '0'); //补0

        }

        private static string computeDigestResponse(string username, string userpwd, string realm, string method, string nonce, string url,string nc,string cnonce,string qop)
        {
            //RF2617
            服务器计算出的摘要
            //StringresponseBefore = ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2;
            //HA1=MD5(A1)=MD5(username:realm:password)  
            //如果 qop 值为“auth”或未指定,那么 HA2 为  
            //HA2=MD5(A2)=MD5(method:digestURI)  
            //如果 qop 值为“auth-int”,那么 HA2 为  
            //HA2=MD5(A2)=MD5(method:digestURI:MD5(entityBody))  
            //如果 qop 值为“auth”或“auth-int”,那么如下计算 response:  
            //response=MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)  
            //如果 qop 未指定,那么如下计算 response:  
            //response=MD5(HA1:nonce:HA2)  
            string ha1 = md5(username + ":" + realm + ":" + userpwd);
            string ha2 = md5(method + ":" + url);
            string response = md5(ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2);
            return response;
        }

要注意的是,拼接HEAD 值的时候  不能去掉 每个参数后的空格,为了好看我去掉空格重试的时候发现又成未验证状态了,补回空格后正常了。 C#现在确实用的公司越来越少了,我也开始在逐步转JAVA中。

 

 

 

你可能感兴趣的:(C#学习记录,工作)