马上加入IBC程序猿 各种源码随意下,各种教程随便看! 注册 每日签到 加入编程讨论群

C#教程 ASP.NET教程 C#视频教程程序源码享受不尽 C#技术求助 ASP.NET技术求助

【源码下载】 社群合作 申请版主 程序开发 【远程协助】 每天乐一乐 每日签到 【承接外包项目】 面试-葵花宝典下载

官方一群:

官方二群:

WebApi接口安全性 接口权限调用、参数防篡改防止恶意调用

[复制链接]
查看4343 | 回复0 | 2019-11-8 09:51:51 | 显示全部楼层 |阅读模式

背景先容

迩来使用WebApi开辟一套对外接口,重要是数据的外送以及效果回传,接口没什么难度,接纳WebApi+EF的架构简朴创建一个模板工程,使用template天生一套WebApi接口,去掉put、delete等操纵,修改一下就可以上线。这些都不在话下,反正网上一大堆教程,随便找谁人step by step做下来就可以了。

然后发布上线后,接口是放在外网,面对两个题目:

  1. 怎样包管接口的调用的正当性
  2. 怎样包管接口及数据的安全性

其实这两个题目是相互团结的,先包管正当,然后在正当底子上包管哀求的唯一性,制止参数被篡改。

鉴于接口上线限期紧迫,团结浩繁案例,先管理掉接口调用数据的安全性题目,这里接纳了RSA报文加解密的方案,包管数据安全和防止接口被恶意调用以及参数篡改的题目。

本文参考博客园多篇博文,内容多有引用,文末附有参照博文的地址。

以下为正文!

正文

起首,接口面对的题目:

  1. 哀求泉源(身份)是否正当(部门管理,后续在处置惩罚)
  2. 哀求参数被篡改?
  3. 哀求的唯一性(不可复制),防止哀求被恶意攻击

管理方案:

  1. 参数加密: 客户端和服务端参数接纳RSA加密后通报,原则上只有持有私钥的服务端才气解密客户端公钥加密的参数,制止了参数篡改的题目
  2. 哀求署名:接纳一套署名算法,对哀求举行署名验证,包管哀求的唯一性

这里参照了WebAPi使用公钥私钥加密先容和使用 一文,举行公钥私钥加解密的处置惩罚

先说服务端:

扩展 MessageProcessingHandler

先看一下MessageProcessingHandler的先容:

  1. #region 程序集 System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
  2. // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Net.Http.dll
  3. #endregion
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. namespace System.Net.Http
  7. {
  8. //
  9. // 择要:
  10. // 仅对哀求和/或相应消息举行一些小型处置惩罚的处置惩罚程序的基类。
  11. public abstract class MessageProcessingHandler : DelegatingHandler
  12. {
  13. //
  14. // 择要:
  15. // 创建的一个实例 System.Net.Http.MessageProcessingHandler 类。
  16. protected MessageProcessingHandler();
  17. //
  18. // 择要:
  19. // 创建的一个实例 System.Net.Http.MessageProcessingHandler 具有特定的内部处置惩罚程序类。
  20. //
  21. // 参数:
  22. // innerHandler:
  23. // 内部处置惩罚程序负责处置惩罚 HTTP 相应消息。
  24. protected MessageProcessingHandler(HttpMessageHandler innerHandler);
  25. //
  26. // 择要:
  27. // 处置惩罚每个发送到服务器的哀求。
  28. //
  29. // 参数:
  30. // request:
  31. // 要处置惩罚的 HTTP 哀求消息。
  32. //
  33. // cancellationToken:
  34. // 可由其他对象或线程用以吸收取消关照的取消标志。
  35. //
  36. // 返回效果:
  37. // 已处置惩罚的 HTTP 哀求消息。
  38. protected abstract HttpRequestMessage <strong><font style="background-color: #ff0000">ProcessRequest</font></strong>(HttpRequestMessage request, CancellationToken cancellationToken);
  39. //
  40. // 择要:
  41. // 处置惩罚来自服务器的每个相应。
  42. //
  43. // 参数:
  44. // response:
  45. // 要处置惩罚的 HTTP 相应消息。
  46. //
  47. // cancellationToken:
  48. // 可由其他对象或线程用以吸收取消关照的取消标志。
  49. //
  50. // 返回效果:
  51. // 已处置惩罚的 HTTP 相应消息。
  52. protected abstract HttpResponseMessage <font style="background-color: #ff0000">ProcessResponse</font>(HttpResponseMessage response, CancellationToken cancellationToken);
  53. //
  54. // 择要:
  55. // 异步发送 HTTP 哀求到要发送到服务器的内部处置惩罚程序。
  56. //
  57. // 参数:
  58. // request:
  59. // 要发送到服务器的 HTTP 哀求消息。
  60. //
  61. // cancellationToken:
  62. // 可由其他对象或线程用以吸收取消关照的取消标志。
  63. //
  64. // 返回效果:
  65. // 体现异步操纵的使命对象。
  66. //
  67. // 异常:
  68. // T:System.ArgumentNullException:
  69. // request 是 null。
  70. protected internal sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
  71. }
  72. }
复制代码

扩展这个类的目标是解密参数,其实也可以推迟到Action过滤器中做,但是照旧以为机遇上在这里处置惩罚比力符合。具体的发起相识一下WebApi消息管道以及扩展过滤器的干系文章,本文不再延伸。

下面是扩展的实今世码:

  1. /// <summary>
  2. /// 哀求预处置惩罚,报文解密
  3. /// </summary>
  4. /// <seealso cref="System.Net.Http.MessageProcessingHandler"/>
  5. public class ArgDecryptMessageProcesssingHandler : MessageProcessingHandler
  6. {
  7. /// <summary>
  8. /// 处置惩罚每个发送到服务器的哀求。
  9. /// </summary>
  10. /// <param name="request"> 要处置惩罚的 HTTP 哀求消息。</param>
  11. /// <param name="cancellationToken">可由其他对象或线程用以吸收取消关照的取消标志。</param>
  12. /// <returns>已处置惩罚的 HTTP 哀求消息。</returns>
  13. protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
  14. {
  15. var contentType = request.Content.Headers.ContentType;
  16. //swagger哀求直接跳过不予处置惩罚
  17. if (request.RequestUri.AbsolutePath.Contains("/swagger"))
  18. {
  19. return request;
  20. }
  21. //得到平台私钥
  22. string privateKey = Common.GetRsaPrivateKey();
  23. //获取Get中的Query信息,解密后重置哀求上下文
  24. if (request.Method == HttpMethod.Get)
  25. {
  26. string baseQuery = request.RequestUri.Query;
  27. if (!string.IsNullOrEmpty(baseQuery))
  28. {
  29. baseQuery = baseQuery.Substring(1);
  30. baseQuery = Regex.Match(baseQuery, "(sign=)*(?<sign>[\\S]+)").Groups[2].Value;
  31. baseQuery = RsaHelper.RSADecrypt(privateKey, baseQuery);
  32. var requestUrl = $"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{baseQuery}";
  33. request.RequestUri = new Uri(requestUrl);
  34. }
  35. }
  36. //获取Post哀求中body中的报文信息,解密后重置哀求上下文
  37. if (request.Method == HttpMethod.Post)
  38. {
  39. string baseContent = request.Content.ReadAsStringAsync().Result;
  40. baseContent = Regex.Match(baseContent, "(sign=)*(?<sign>[\\S]+)").Groups[2].Value;
  41. baseContent = RsaHelper.RSADecrypt(privateKey, baseContent);
  42. request.Content = new StringContent(baseContent);
  43. //此contentType必须末了设置 否则会变成默认值
  44. request.Content.Headers.ContentType = contentType;
  45. }
  46. return request;
  47. }
  48. /// <summary>
  49. /// 处置惩罚来自服务器的每个相应。
  50. /// </summary>
  51. /// <param name="response"> 要处置惩罚的 HTTP 相应消息。</param>
  52. /// <param name="cancellationToken">可由其他对象或线程用以吸收取消关照的取消标志。</param>
  53. /// <returns>已处置惩罚的 HTTP 相应消息。</returns>
  54. protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
  55. {
  56. return response;
  57. }
  58. }
复制代码

获取平台私钥那里,现实上可以针对差别的接口调用方单独一个,另起一篇在先容。

然后找到管理方案【App_Start】目次下的WebApiConfig类,在内里添加如下代码,启用消息处置惩罚扩展类:

  1. public static void Register(HttpConfiguration config)
  2. {
  3. // Web API 路由
  4. config.MapHttpAttributeRoutes();
  5. config.Routes.MapHttpRoute(
  6. name: "DefaultApi",
  7. routeTemplate: "api/{controller}/{id}",
  8. defaults: new { id = RouteParameter.Optional }
  9. );
  10. <strong><font style="background-color: #ff0000">config.MessageHandlers.Add(</font></strong><strong><font style="background-color: #ff0000">new</font></strong><strong><font style="background-color: #ff0000"> ArgDecryptMessageProcesssingHandler());</font></strong>
  11. }
复制代码

扩展 ActionFilterAttribute

注意!注意!注意!

原博文中是扩展的 AuthorizeAttribute,即认证和授权过滤器,代码实现上是没有多大差别的;在机遇上认证和授权过滤器要比方法过滤器实行的要早,更得当做认证和授权的操纵。而我们扩展这个过滤器的目标是对报文举行署名验证以及超时验证,以是使用方法过滤器更适当些。

下面是扩展过滤器的代码:

  1. /// <summary>
  2. /// 扩展方法过滤器,进入方法前验证署名
  3. /// </summary>
  4. public class ApiVerifyFilter : ActionFilterAttribute
  5. {
  6. public override void OnActionExecuting(HttpActionContext actionContext)
  7. {
  8. base.OnActionExecuting(actionContext);
  9. //获取平台私钥
  10. string privateKey = Common.GetRsaPrivateKey();
  11. //获取哀求的超时时间,为了测试设置为100秒,即两次调用隔断不能高出100秒
  12. string expireyTime = ConfigurationManager.AppSettings["UrlExpireTime"];
  13. var request = actionContext.Request;
  14. //验证署名所需header内容
  15. if (!request.Headers.Contains("signature") || !request.Headers.Contains("timestamp") || !request.Headers.Contains("nonce"))
  16. {
  17. SetSpecialResponseMessage(actionContext, 40301);
  18. return;
  19. }
  20. var token = string.Empty;
  21. var signature = request.Headers.GetValues("signature").FirstOrDefault();
  22. var timeStamp = request.Headers.GetValues("timestamp").FirstOrDefault();
  23. var nonce = request.Headers.GetValues("nonce").FirstOrDefault();
  24. //验证署名
  25. if (!Common.SignValidate(privateKey, nonce, timeStamp, signature, token))
  26. {
  27. SetSpecialResponseMessage(actionContext, 40302);
  28. return;
  29. }
  30. //查抄接口调用是否超时
  31. var ts = Common.DateTime2TimeStamp(DateTime.UtcNow) - Convert.ToDouble(timeStamp);
  32. if (ts > int.Parse(expireyTime) * 1000)
  33. {
  34. SetSpecialResponseMessage(actionContext, 40303);
  35. return;
  36. }
  37. }
  38. /// <summary>
  39. /// 设置署名验证异常返回状态
  40. /// </summary>
  41. /// <param name="actionContext">当前哀求上下文</param>
  42. /// <param name="statusCode">异常状态码</param>
  43. private static void SetSpecialResponseMessage(HttpActionContext actionContext, int statusCode)
  44. {
  45. BizResponseModel model = new BizResponseModel
  46. {
  47. Status = statusCode,
  48. Date = DateTime.Now.ToString("yyyyMMddhhmmssfff"),
  49. Message = "服务端拒绝访问"
  50. };
  51. switch (statusCode)
  52. {
  53. case 40301:
  54. model.Message = "没有设置署名、时间戳、随机字符串";
  55. break;
  56. case 40302:
  57. model.Message = "署名无效";
  58. break;
  59. case 40303:
  60. model.Message = "无效的哀求";
  61. break;
  62. default:
  63. break;
  64. }
  65. actionContext.Response = new HttpResponseMessage
  66. {
  67. Content = new StringContent(JsonConvert.SerializeObject(model))
  68. };
  69. }
  70. public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
  71. {
  72. base.OnActionExecuted(actionExecutedContext);
  73. }
  74. }
复制代码

这里为了方便写了个ResponseModel,代码如下:

  1. /// <summary>
  2. /// 特殊状态
  3. /// </summary>
  4. public class BizResponseModel
  5. {
  6. public int Status { get; set; }
  7. public string Message { get; set; }
  8. public string Date { get; set; }
  9. }
复制代码

然后下面是用的公共方法:

  1. /// <summary>
  2. /// 获取时间戳毫秒数
  3. /// </summary>
  4. /// <param name="dateTime"></param>
  5. /// <returns></returns>
  6. public static long DateTime2TimeStamp(DateTime dateTime)
  7. {
  8. TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
  9. return Convert.ToInt64(ts.TotalMilliseconds);
  10. }
  11. public static bool SignValidate(string privateKey, string nonce, string timestamp, string signature, string token)
  12. {
  13. bool isValidate = false;
  14. var tempSign = RsaHelper.RSADecrypt(privateKey, signature);
  15. string[] arr = new[] { token, timestamp, nonce }.OrderBy(z => z).ToArray();
  16. string arrString = string.Join("", arr);
  17. var sha256Result = arrString.EncryptSha256();
  18. if (sha256Result == tempSign)
  19. {
  20. isValidate = true;
  21. }
  22. return isValidate;
  23. }
复制代码

署名验证的过程如下:

  1. 获取到报文Header中的 nonce、timestamp、signature、token信息
  2. 将token、timestamp、nonce 三者归并数组中,然后举行序次排序(排序为了包管后续三个字符串拼接后一致)
  3. 将数组拼接成字符串,然后举行sha256 哈希运算(这里随便什么运算都行,重要为了防止超长加密贫苦)
  4. 将上一步的哈希效果与[signature] RSA解密效果举行比对,一致则署名验证通过,否则则署名不一致,哀求为伪造


然后,如今需要启用刚添加的方法过滤器,由于是继承与属性,可以全局启用,大概单个Controller中启用、大概为某个Action启用。全局启用代码如下:

下的WebApiConfig类添加如下代码:

  1. config.Filters.Add(new ApiVerifyFilter());
复制代码

OK,全部完成,末了附上两个前后的效果对比!

参考博文:

WebApi安全性 使用TOKEN+署名验证

WebAPi接口安全之公钥私钥加密

使用OAuth打造webapi认证服务供自己的客户端使用

Asp.Net WebAPI中Filter过滤器的使用以及实行序次

微信 公众号开辟文档

写博文太累了,回家吃螃蟹补补~

C#论坛 www.ibcibc.com IBC编程社区
C#
C#论坛
IBC编程社区
*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则