WebApi接口安全认证

摘要访问认证是一种协议规定的Web服务器用来同网页浏览器进行认证信息协商的方法。它在密码发出前,先对其应用哈希函数,这相对于HTTP基本认证发送明文而言,更安全。从技术上讲,摘要认证是使用随机数来阻止进行密码分析的MD5加密哈希函数应用。它使用HTTP协议。

一、摘要认证基本流程:

1.客户端请求 (无认证)

Html代码  

  1. GET /dir/index.html HTTP/1.0
  2. 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="[email protected]",
  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="[email protected]",
  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代码  

  1. 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. Header header = new Header(request.Headers.Authorization.Parameter, request.Method.Method);
  11. if (Nonce.IsValid(header.Nonce, header.NounceCounter))
  12. {
  13. // Just assuming password is same as username for the purpose of illustration
  14. string password = header.UserName;
  15. string ha1 = String.Format("{0}:{1}:{2}", header.UserName, header.Realm, password).ToMD5Hash();
  16. string ha2 = String.Format("{0}:{1}", header.Method, header.Uri).ToMD5Hash();
  17. string computedResponse = String.Format("{0}:{1}:{2}:{3}:{4}:{5}",
  18. ha1, header.Nonce, header.NounceCounter,header.Cnonce, "auth", ha2).ToMD5Hash();
  19. if (String.CompareOrdinal(header.Response, computedResponse) == 0)
  20. {
  21. // digest computed matches the value sent by client in the response field.
  22. // Looks like an authentic client! Create a principal.
  23. var claims = new List<Claim>
  24. {
  25. new Claim(ClaimTypes.Name, header.UserName),
  26. new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)
  27. };
  28. ClaimsPrincipal principal = new ClaimsPrincipal(new[] { new ClaimsIdentity(claims, "Digest") });
  29. Thread.CurrentPrincipal = principal;
  30. if (HttpContext.Current != null)
  31. HttpContext.Current.User = principal;
  32. }
  33. }
  34. }
  35. HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
  36. if (response.StatusCode == HttpStatusCode.Unauthorized)
  37. {
  38. response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));
  39. }
  40. return response;
  41. }
  42. catch (Exception)
  43. {
  44. var response = request.CreateResponse(HttpStatusCode.Unauthorized);
  45. response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Digest",Header.UnauthorizedResponseHeader.ToString()));
  46. return response;
  47. }
  48. }
  49. }

Header类

C#代码  

  1. public class Header
  2. {
  3. public Header() { }
  4. public Header(string header, string method)
  5. {
  6. string keyValuePairs = header.Replace("\"", String.Empty);
  7. foreach (string keyValuePair in keyValuePairs.Split(‘,‘))
  8. {
  9. int index = keyValuePair.IndexOf("=", System.StringComparison.Ordinal);
  10. string key = keyValuePair.Substring(0, index);
  11. string value = keyValuePair.Substring(index + 1);
  12. switch (key)
  13. {
  14. case "username": this.UserName = value; break;
  15. case "realm": this.Realm = value; break;
  16. case "nonce": this.Nonce = value; break;
  17. case "uri": this.Uri = value; break;
  18. case "nc": this.NounceCounter = value; break;
  19. case "cnonce": this.Cnonce = value; break;
  20. case "response": this.Response = value; break;
  21. case "method": this.Method = value; break;
  22. }
  23. }
  24. if (String.IsNullOrEmpty(this.Method))
  25. this.Method = method;
  26. }
  27. public string Cnonce { get; private set; }
  28. public string Nonce { get; private set; }
  29. public string Realm { get; private set; }
  30. public string UserName { get; private set; }
  31. public string Uri { get; private set; }
  32. public string Response { get; private set; }
  33. public string Method { get; private set; }
  34. public string NounceCounter { get; private set; }
  35. // This property is used by the handler to generate a
  36. // nonce and get it ready to be packaged in the
  37. // WWW-Authenticate header, as part of 401 response
  38. public static Header UnauthorizedResponseHeader
  39. {
  40. get
  41. {
  42. return new Header()
  43. {
  44. Realm = "MyRealm",
  45. Nonce = WebApiDemo.Nonce.Generate()
  46. };
  47. }
  48. }
  49. public override string ToString()
  50. {
  51. StringBuilder header = new StringBuilder();
  52. header.AppendFormat("realm=\"{0}\"", Realm);
  53. header.AppendFormat(",nonce=\"{0}\"", Nonce);
  54. header.AppendFormat(",qop=\"{0}\"", "auth");
  55. return header.ToString();
  56. }
  57. }

nonce类

C#代码  

  1. public class Nonce
  2. {
  3. private static ConcurrentDictionary<string, Tuple<int, DateTime>>
  4. nonces = new ConcurrentDictionary<string, Tuple<int, DateTime>>();
  5. public static string Generate()
  6. {
  7. byte[] bytes = new byte[16];
  8. using (var rngProvider = new RNGCryptoServiceProvider())
  9. {
  10. rngProvider.GetBytes(bytes);
  11. }
  12. string nonce = bytes.ToMD5Hash();
  13. nonces.TryAdd(nonce, new Tuple<int, DateTime>(0, DateTime.Now.AddMinutes(10)));
  14. return nonce;
  15. }
  16. public static bool IsValid(string nonce, string nonceCount)
  17. {
  18. Tuple<int, DateTime> cachedNonce = null;
  19. //nonces.TryGetValue(nonce, out cachedNonce);
  20. nonces.TryRemove(nonce, out cachedNonce);//每个nonce只允许使用一次
  21. if (cachedNonce != null) // nonce is found
  22. {
  23. // nonce count is greater than the one in record
  24. if (Int32.Parse(nonceCount) > cachedNonce.Item1)
  25. {
  26. // nonce has not expired yet
  27. if (cachedNonce.Item2 > DateTime.Now)
  28. {
  29. // update the dictionary to reflect the nonce count just received in this request
  30. //nonces[nonce] = new Tuple<int, DateTime>(Int32.Parse(nonceCount), cachedNonce.Item2);
  31. // Every thing looks ok - server nonce is fresh and nonce count seems to be
  32. // incremented. Does not look like replay.
  33. return true;
  34. }
  35. }
  36. }
  37. return false;
  38. }
  39. }

需要使用摘要验证可在代码里添加Attribute [Authorize],如:

C#代码  

  1. [Authorize]
  2. public class ProductsController : ApiController

最后Global.asax里需注册下

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. sMessage = "";
  7. using (System.Net.WebClient client = new System.Net.WebClient())
  8. {
  9. client.Credentials = CreateAuthenticateValue(sUrl);
  10. client.Headers = CreateHeader(sContentType);
  11. Uri url = new Uri(sUrl);
  12. byte[] bytes = Encoding.UTF8.GetBytes(sEntity);
  13. byte[] buffer;
  14. switch (sMethod.ToUpper())
  15. {
  16. case "GET":
  17. buffer = client.DownloadData(url);
  18. break;
  19. case "POST":
  20. buffer = client.UploadData(url, "POST", bytes);
  21. break;
  22. default:
  23. buffer = client.UploadData(url, "POST", bytes);
  24. break;
  25. }
  26. return Encoding.UTF8.GetString(buffer);
  27. }
  28. }
  29. catch (WebException ex)
  30. {
  31. sMessage = ex.Message;
  32. var rsp = ex.Response as HttpWebResponse;
  33. var httpStatusCode = rsp.StatusCode;
  34. var authenticate = rsp.Headers.Get("WWW-Authenticate");
  35. return "";
  36. }
  37. catch (Exception ex)
  38. {
  39. sMessage = ex.Message;
  40. return "";
  41. }
  42. }

关键代码,在这里添加用户认证,使用NetworkCredential

C#代码  

  1. private static CredentialCache CreateAuthenticateValue(string sUrl)
  2. {
  3. CredentialCache credentialCache = new CredentialCache();
  4. credentialCache.Add(new Uri(sUrl), "Digest", new NetworkCredential("Lime", "Lime"));
  5. return credentialCache;
  6. }
时间: 2024-11-14 12:20:58

WebApi接口安全认证的相关文章

ASP.NET Core WebApi基于Redis实现Token接口安全认证

一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebService服务中可以通过SoapHead验证机制来实现,那么在ASP.NET Core WebApi中我们应该如何保证我们的接口安全呢?  近年来RESTful API开始风靡,使用HTTP header来传递认证令牌似乎变得理所应当,而单页应用(SPA).前后端分离架构似乎正在促成越来越多的WEB应

WebApi 接口参数不再困惑:传参详解

转自:http://www.cnblogs.com/landeanfen/p/5337072.html 阅读目录 一.get请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.“怪异”的get请求 二.post请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 4.后台发送请求参数的传递 三.put请求 1.基础类型参数 2.实体作为参数 3.数组作为参数 四.delete请求 五.总结 正文 前言:还记得刚使用WebApi那会儿,被它的传参机制折腾了好久,查阅了半天资料.如

[转]C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解

本文转自:http://www.cnblogs.com/landeanfen/p/5501487.html 阅读目录 一.void无返回值 二.IHttpActionResult 1.Json(T content) 2.Ok(). Ok(T content) 3.NotFound() 4.其他 5.自定义IHttpActionResult接口的实现 三.HttpResponseMessage 四.自定义类型 五.总结 正文 前言:已经有一个月没写点什么了,感觉心里空落落的.今天再来篇干货,想要学

WebAPI接口安全校验

通过网上查看相关WebAPI接口验证的方法,整理了一下,直接上代码,功能不复杂,有问题留言, //----------------------------------------------------------------------- // <copyright file="ApiAuthenticationFilter.cs" company="FenSiShengHuo, Ltd."> // Copyright (c) 2018 , All r

WebApi接口 - 如何在应用中调用webapi接口

简单做个webapi(查询+添加)接口 首先,我们需要有一个webapi接口项目,我这里以前面WebApi接口 - 响应输出xml和json文章的项目来构建本篇文章的测试用例:这里新建一个 DbData 数据源类,主要用来做数据存储和提供查询列表数据及添加数据方法,具体代码如:  1 public class DbData 2     { 3         public static DbData Current 4         { 5             get 6         

app与php后台接口登录认证、验证(seesion和token)

简要:随着电商的不断发展,APP也层次不穷,随着科技的发展主要登录形式(微信.QQ.账号/密码):为此向大家分享一下"app与php后台接口登录认证.验证"想法和做法:希望能够帮助困惑的伙伴们,如果有不对或者好的建议告知下:*~*!  一.登录机制 粗略分析:登录可分为三个阶段(登录验证.登录持续.退出登录):登录验证指客户端提供账号/密码(或第三方平台(微信.qq)获取openid/unionid)向服务器提出登录请求,服务器应答请求判断能否登录并返回相应数据:登录持续指客户端登录后

如何使用程序调用webApi接口

如何使用程序调用webApi接口 在C#中,传统调用HTTP接口一般有两种办法: WebRequest/WebResponse组合的方法调用 WebClient类进行调用. 第一种方法抽象程度较低,使用较为繁琐:而WebClient主要面向了WEB网页场景,在模拟Web操作时使用较为方便,但用在RestFul场景下却比较麻烦,在Web API发布的同时,.NET提供了两个程序集:System.Net.Http和System.Net.Http.Formatting.这两个程序集中最核心的类是Htt

C#进阶系列——WebApi 接口参数不再困惑:传参详解

C#进阶系列--WebApi 接口参数不再困惑:传参详解

C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解

原文地址:http://www.cnblogs.com/landeanfen/p/5501487.html 使用过Webapi的园友应该都知道,Webapi的接口返回值主要有四种类型 void无返回值 IHttpActionResult HttpResponseMessage 自定义类型 此篇就围绕这四块分别来看看它们的使用. 一.void无返回值 void关键字我们都不陌生,它申明方法没有返回值.它的使用也很简单,我们来看一个示例就能明白. public class ORDER { publi