摘要访问认证是一种协议规定的Web服务器用来同网页浏览器进行认证信息协商的方法。它在密码发出前,先对其应用哈希函数,这相对于HTTP基本认证发送明文而言,更安全。从技术上讲,摘要认证是使用随机数来阻止进行密码分析的MD5加密哈希函数应用。它使用HTTP协议。
一、摘要认证基本流程:
1.客户端请求 (无认证)
Html代码
- GET /dir/index.html HTTP/1.0
- Host: localhost
2.服务器响应
服务端返回401未验证的状态,并且返回WWW-Authenticate信息,包含了验证方式Digest,realm,qop,nonce,opaque的值。其中:
Digest:认证方式;
realm:领域,领域参数是强制的,在所有的盘问中都必须有,它的目的是鉴别SIP消息中的机密,在SIP实际应用中,它通常设置为SIP代理服务器所负责的域名;
qop:保护的质量,这个参数规定服务器支持哪种保护方案,客户端可以从列表中选择一个。值 “auth”表示只进行身份查验, “auth-int”表示进行查验外,还有一些完整性保护。需要看更详细的描述,请参阅RFC2617;
nonce:为一串随机值,在下面的请求中会一直使用到,当过了存活期后服务端将刷新生成一个新的nonce值;
opaque:一个不透明的(不让外人知道其意义)数据字符串,在盘问中发送给用户。
Html代码
1. HTTP/1.0 401 Unauthorized
2. Server: HTTPd/0.9
3. Date: Sun, 10 Apr 2005 20:26:47 GMT
4. WWW-Authenticate: Digest realm="testrealm@host.com",
5. qop="auth,auth-int",
6. nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
7. opaque="5ccc069c403ebaf9f0171e9517f40e41"
3.客户端请求 (用户名 "Mufasa", 密码 "Circle Of Life")
客户端接受到请求返回后,进行HASH运算,返回Authorization参数
其中:realm,nonce,qop由服务器产生;
uri:客户端想要访问的URI;
nc:“现时”计数器,这是一个16进制的数值,即客户端发送出请求的数量(包括当前这个请求),这些请求都使用了当前请求中这个“现时”值。例如,对一个给定的“现时”值,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的,是让服务器保持这个计数器的一个副本,以便检测重复的请求。如果这个相同的值看到了两次,则这个请求是重复的;
cnonce:这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护;
response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令。
Html代码
1. <strong>response计算过程:</strong>
2. HA1=MD5(A1)=MD5(username:realm:password)
3. 如果 qop 值为“auth”或未指定,那么 HA2 为
4. HA2=MD5(A2)=MD5(method:digestURI)
5. 如果 qop 值为“auth-int”,那么 HA2 为
6. HA2=MD5(A2)=MD5(method:digestURI:MD5(entityBody))
7. 如果 qop 值为“auth”或“auth-int”,那么如下计算 response:
8. response=MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2)
9. 如果 qop 未指定,那么如下计算 response:
10. response=MD5(HA1:nonce:HA2)
请求头:
Html代码
1. GET /dir/index.html HTTP/1.0
2. Host: localhost
3. Authorization: Digest username="Mufasa",
4. realm="testrealm@host.com",
5. nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
6. uri="/dir/index.html",
7. qop=auth,
8. nc=00000001,
9. cnonce="0a4f113b",
10. response="6629fae49393a05397450978507c4ef1",
11. opaque="5ccc069c403ebaf9f0171e9517f40e41"
4.服务器响应
当服务器接收到摘要响应,也要重新计算响应中各参数的值,并利用客户端提供的参数值,和服务器上存储的口令,进行比对。如果计算结果与收到的客户响应值是相同的,则客户已证明它知道口令,因而客户的身份验证通过。
Html代码
- HTTP/1.0 200 OK
二、服务端验证
编写一个自定义消息处理器,需要通过System.Net.Http.DelegatingHandler进行派生,并重写SendAsync方法。
C#代码
1. public class AuthenticationHandler : DelegatingHandler
2. {
3. protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
4. {
5. try
6. {
7. HttpRequestHeaders headers = request.Headers;
8. if (headers.Authorization != null)
9. {
10. new Header(request.Headers.Authorization.Parameter, request.Method.Method);
11.
12. if (Nonce.IsValid(header.Nonce, header.NounceCounter))
13. {
14. // Just assuming password is same as username for the purpose of illustration
15. string password = header.UserName;
16.
17. string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, password).ToMD5Hash();
18.
19. string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash();
20.
21. string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
22. "auth", ha2).ToMD5Hash();
23.
24. if (String.CompareOrdinal(header.Response, computedResponse) == 0)
25. {
26. // digest computed matches the value sent by client in the response field.
27. // Looks like an authentic client! Create a principal.
28. new List<Claim>
29. {
30. new Claim(ClaimTypes.Name, header.UserName),
31. new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)
32. };
33.
34. new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") });
35.
36. Thread.CurrentPrincipal = principal;
37.
38. if (HttpContext.Current != null)
39. HttpContext.Current.User = principal;
40. }
41. }
42. }
43.
44. base.SendAsync(request, cancellationToken);
45.
46. if (response.StatusCode == HttpStatusCode.Unauthorized)
47. {
48. new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));
49. }
50.
51. return response;
52. }
53. catch (Exception)
54. {
55. var response = request.CreateResponse(HttpStatusCode.Unauthorized);
56. new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));
57.
58. return response;
59. }
60. }
61. }
Header类
C#代码
1. public class Header
2. {
3. public Header() { }
4.
5. public Header(string header, string method)
6. {
7. string keyValuePairs = header.Replace("\"", String.Empty);
8.
9. foreach (string keyValuePair in keyValuePairs.Split(','))
10. {
11. int index = keyValuePair.IndexOf("=", System.StringComparison.Ordinal);
12. string key = keyValuePair.Substring(0, index);
13. string value = keyValuePair.Substring(index + 1);
14.
15. switch (key)
16. {
17. case "username": this.UserName = value; break;
18. case "realm": this.Realm = value; break;
19. case "nonce": this.Nonce = value; break;
20. case "uri": this.Uri = value; break;
21. case "nc": this.NounceCounter = value; break;
22. case "cnonce": this.Cnonce = value; break;
23. case "response": this.Response = value; break;
24. case "method": this.Method = value; break;
25. }
26. }
27.
28. if (String.IsNullOrEmpty(this.Method))
29. this.Method = method;
30. }
31.
32. public string Cnonce { get; private set; }
33. public string Nonce { get; private set; }
34. public string Realm { get; private set; }
35. public string UserName { get; private set; }
36. public string Uri { get; private set; }
37. public string Response { get; private set; }
38. public string Method { get; private set; }
39. public string NounceCounter { get; private set; }
40.
41. // This property is used by the handler to generate a
42. // nonce and get it ready to be packaged in the
43. // WWW-Authenticate header, as part of 401 response
44. public static Header UnauthorizedResponseHeader
45. {
46. get
47. {
48. return new Header()
49. {
50. "MyRealm",
51. Nonce = WebApiDemo.Nonce.Generate()
52. };
53. }
54. }
55.
56. public override string ToString()
57. {
58. new StringBuilder();
59. "realm=\"{0}\"", Realm);
60. ",nonce=\"{0}\"", Nonce);
61. ",qop=\"{0}\"", "auth");
62. return header.ToString();
63. }
- }
nonce类
C#代码
1. public class Nonce
2. {
3. private static ConcurrentDictionary<string, Tuple<int, DateTime>>
4. new ConcurrentDictionary<string, Tuple<int, DateTime>>();
5.
6. public static string Generate()
7. {
8. byte[] bytes = new byte[16];
9.
10. using (var rngProvider = new RNGCryptoServiceProvider())
11. {
12. rngProvider.GetBytes(bytes);
13. }
14.
15. string nonce = bytes.ToMD5Hash();
16.
17. new Tuple<int, DateTime>(0, DateTime.Now.AddMinutes(10)));
18.
19. return nonce;
20. }
21.
22. public static bool IsValid(string nonce, string nonceCount)
23. {
24. int, DateTime> cachedNonce = null;
25. //nonces.TryGetValue(nonce, out cachedNonce);
26. out cachedNonce);//每个nonce只允许使用一次
27.
28. if (cachedNonce != null) // nonce is found
29. {
30. // nonce count is greater than the one in record
31. if (Int32.Parse(nonceCount) > cachedNonce.Item1)
32. {
33. // nonce has not expired yet
34. if (cachedNonce.Item2 > DateTime.Now)
35. {
36. // update the dictionary to reflect the nonce count just received in this request
37. //nonces[nonce] = new Tuple<int, DateTime>(Int32.Parse(nonceCount), cachedNonce.Item2);
38.
39. // Every thing looks ok - server nonce is fresh and nonce count seems to be
40. // incremented. Does not look like replay.
41. return true;
42. }
43.
44. }
45. }
46.
47. return false;
48. }
49. }
C#代码
1. [Authorize]
2. public class ProductsController : ApiController
C#代码
1. GlobalConfiguration.Configuration.MessageHandlers.Add(
2. new AuthenticationHandler());
三、客户端的调用
这里主要说明使用WebClient调用
C#代码
1. public static string Request(string sUrl, string sMethod, string sEntity, string sContentType,
2. out string sMessage)
3. {
4. try
5. {
6. "";
7. using (System.Net.WebClient client = new System.Net.WebClient())
8. {
9. client.Credentials = CreateAuthenticateValue(sUrl);
10. client.Headers = CreateHeader(sContentType);
11.
12. new Uri(sUrl);
13. byte[] bytes = Encoding.UTF8.GetBytes(sEntity);
14. byte[] buffer;
15. switch (sMethod.ToUpper())
16. {
17. case "GET":
18. buffer = client.DownloadData(url);
19. break;
20. case "POST":
21. "POST", bytes);
22. break;
23. default:
24. "POST", bytes);
25. break;
26. }
27.
28. return Encoding.UTF8.GetString(buffer);
29. }
30. }
31. catch (WebException ex)
32. {
33. sMessage = ex.Message;
34. as HttpWebResponse;
35. var httpStatusCode = rsp.StatusCode;
36. "WWW-Authenticate");
37.
38. return "";
39. }
40. catch (Exception ex)
41. {
42. sMessage = ex.Message;
43. return "";
44. }
45. }
C#代码
1. private static CredentialCache CreateAuthenticateValue(string sUrl)
2. {
3. new CredentialCache();
4. new Uri(sUrl), "Digest", new NetworkCredential("Lime", "Lime"));
5.
6. return credentialCache;
7. }
网站网址:lscfan.com