请选择 进入手机版 | 继续访问电脑版

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

C#教程 ASP.NET教程 C#视频教程程序源码享受不尽 C#问题入口 ASP.NET问题入口

【C#问题提交】 社群合作 申请版主 程序开发 【远程协助】 每天乐一乐 每日签到 【承接毕业设计】 面试-葵花宝典下载

官方一群:

官方二群:

查看: 494|回复: 0

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

[复制链接]
  • TA的每日心情
    开心
    6 天前
  • 签到天数: 1608 天

    [LV.Master]伴坛终老

    4251

    主题

    6175

    帖子

    11万

    积分

    管理员

    IBC编程社区-原道楠

    Rank: 9Rank: 9Rank: 9

    积分
    111214

    推广达人突出贡献优秀版主荣誉管理论坛元老

    发表于 2019-11-8 09:51:51 | 显示全部楼层 |阅读模式

    马上加入IBC,查看更多教程

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x

    背景先容

    迩来使用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编程社区
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则