背景 :
最近在对接一个公安局数据接口相关的这块业务,基于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中。