ibcadmin 发表于 2019-11-8 09:51:51

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

<h1>背景先容</h1> <p>迩来使用WebApi开辟一套对外接口,重要是数据的外送以及效果回传,接口没什么难度,接纳WebApi+EF的架构简朴创建一个模板工程,使用template天生一套WebApi接口,去掉put、delete等操纵,修改一下就可以上线。这些都不在话下,反正网上一大堆教程,随便找谁人step by step做下来就可以了。</p> <p> </p> <p>然后发布上线后,接口是放在外网,面对两个题目:</p> <ol> <li>怎样包管接口的调用的正当性<li><strong>怎样包管接口及数据的安全性</strong></li></ol> <p>其实这两个题目是相互团结的,先包管正当,然后在正当底子上包管哀求的唯一性,制止参数被篡改。</p> <p>鉴于接口上线限期紧迫,团结浩繁案例,先管理掉接口调用数据的安全性题目,这里接纳了RSA报文加解密的方案,包管数据安全和防止接口被恶意调用以及参数篡改的题目。</p> <p>本文参考博客园多篇博文,内容多有引用,文末附有参照博文的地址。</p> <p>以下为正文!</p> <h1>正文</h1> <h2>起首,接口面对的题目:</h2> <ol> <li><strike>哀求泉源(身份)是否正当(部门管理,后续在处置惩罚)</strike>?<li>哀求参数被篡改?<li>哀求的唯一性(不可复制),防止哀求被恶意攻击</li></ol> <p> </p> <h2>管理方案:</h2> <p> </p> <ol> <li>参数加密: 客户端和服务端参数接纳RSA加密后通报,原则上只有<strong>持有私钥的服务端</strong>才气解密<strong>客户端公钥加密</strong>的参数,制止了参数篡改的题目<li>哀求署名:接纳一套署名算法,对哀求举行署名验证,包管哀求的唯一性</li></ol> <p> </p> <p>这里参照了<a href="https://www.cnblogs.com/clly/p/7384008.html">WebAPi使用公钥私钥加密先容和使用</a> 一文,举行公钥私钥加解密的处置惩罚</p> <p><strong>先说服务端:</strong></p> <h3>扩展 MessageProcessingHandler</h3> <p>先看一下<font style="background-color: #ffff00">MessageProcessingHandler</font>的先容:</p> #region 程序集 System.Net.Http, Version=4.2.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.Net.Http.dll
#endregion

using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Http
{
    //
    // 择要:
    //   仅对哀求和/或相应消息举行一些小型处置惩罚的处置惩罚程序的基类。
    public abstract class MessageProcessingHandler : DelegatingHandler
    {
      //
      // 择要:
      //   创建的一个实例 System.Net.Http.MessageProcessingHandler 类。
      protected MessageProcessingHandler();
      //
      // 择要:
      //   创建的一个实例 System.Net.Http.MessageProcessingHandler 具有特定的内部处置惩罚程序类。
      //
      // 参数:
      //   innerHandler:
      //   内部处置惩罚程序负责处置惩罚 HTTP 相应消息。
      protected MessageProcessingHandler(HttpMessageHandler innerHandler);

      //
      // 择要:
      //   处置惩罚每个发送到服务器的哀求。
      //
      // 参数:
      //   request:
      //   要处置惩罚的 HTTP 哀求消息。
      //
      //   cancellationToken:
      //   可由其他对象或线程用以吸收取消关照的取消标志。
      //
      // 返回效果:
      //   已处置惩罚的 HTTP 哀求消息。
      protected abstract HttpRequestMessage <strong><font style="background-color: #ff0000">ProcessRequest</font></strong>(HttpRequestMessage request, CancellationToken cancellationToken);
      //
      // 择要:
      //   处置惩罚来自服务器的每个相应。
      //
      // 参数:
      //   response:
      //   要处置惩罚的 HTTP 相应消息。
      //
      //   cancellationToken:
      //   可由其他对象或线程用以吸收取消关照的取消标志。
      //
      // 返回效果:
      //   已处置惩罚的 HTTP 相应消息。
      protected abstract HttpResponseMessage <font style="background-color: #ff0000">ProcessResponse</font>(HttpResponseMessage response, CancellationToken cancellationToken);
      //
      // 择要:
      //   异步发送 HTTP 哀求到要发送到服务器的内部处置惩罚程序。
      //
      // 参数:
      //   request:
      //   要发送到服务器的 HTTP 哀求消息。
      //
      //   cancellationToken:
      //   可由其他对象或线程用以吸收取消关照的取消标志。
      //
      // 返回效果:
      //   体现异步操纵的使命对象。
      //
      // 异常:
      //   T:System.ArgumentNullException:
      //   request 是 null。
      protected internal sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
    }
}
<p> </p>
<p>扩展这个类的目标是<strong>解密参数</strong>,其实也可以推迟到Action过滤器中做,但是照旧以为机遇上在这里处置惩罚比力符合。具体的发起相识一下WebApi消息管道以及扩展过滤器的干系文章,本文不再延伸。</p>
<p>下面是扩展的实今世码:</p>
<p> </p>
/// <summary>
    /// 哀求预处置惩罚,报文解密
    /// </summary>
    /// <seealso cref="System.Net.Http.MessageProcessingHandler"/>
    public class ArgDecryptMessageProcesssingHandler : MessageProcessingHandler
    {

      /// <summary>
      /// 处置惩罚每个发送到服务器的哀求。
      /// </summary>
      /// <param name="request">          要处置惩罚的 HTTP 哀求消息。</param>
      /// <param name="cancellationToken">可由其他对象或线程用以吸收取消关照的取消标志。</param>
      /// <returns>已处置惩罚的 HTTP 哀求消息。</returns>
      protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
      {
            var contentType = request.Content.Headers.ContentType;

            //swagger哀求直接跳过不予处置惩罚
            if (request.RequestUri.AbsolutePath.Contains("/swagger"))
            {
                return request;
            }

            //得到平台私钥
            string privateKey = Common.GetRsaPrivateKey();

            //获取Get中的Query信息,解密后重置哀求上下文
            if (request.Method == HttpMethod.Get)
            {
                string baseQuery = request.RequestUri.Query;
                if (!string.IsNullOrEmpty(baseQuery))
                {
                  baseQuery = baseQuery.Substring(1);
                  baseQuery = Regex.Match(baseQuery, "(sign=)*(?<sign>[\\S]+)").Groups.Value;
                  baseQuery = RsaHelper.RSADecrypt(privateKey, baseQuery);
                  var requestUrl = $"{request.RequestUri.AbsoluteUri.Split('?')}?{baseQuery}";
                  request.RequestUri = new Uri(requestUrl);
                }

            }

            //获取Post哀求中body中的报文信息,解密后重置哀求上下文
            if (request.Method == HttpMethod.Post)
            {
                string baseContent = request.Content.ReadAsStringAsync().Result;
                baseContent = Regex.Match(baseContent, "(sign=)*(?<sign>[\\S]+)").Groups.Value;
                baseContent = RsaHelper.RSADecrypt(privateKey, baseContent);
                request.Content = new StringContent(baseContent);
                //此contentType必须末了设置 否则会变成默认值
                request.Content.Headers.ContentType = contentType;
            }

            return request;
      }

      /// <summary>
      /// 处置惩罚来自服务器的每个相应。
      /// </summary>
      /// <param name="response">         要处置惩罚的 HTTP 相应消息。</param>
      /// <param name="cancellationToken">可由其他对象或线程用以吸收取消关照的取消标志。</param>
      /// <returns>已处置惩罚的 HTTP 相应消息。</returns>
      protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
      {
            return response;
      }
    }

<p>获取平台私钥那里,现实上可以针对差别的接口调用方单独一个,另起一篇在先容。</p>
<p> </p>
<p>然后找到管理方案【App_Start】目次下的<font style="background-color: #ffff00">WebApiConfig</font>类,在内里添加如下代码,启用消息处置惩罚扩展类:</p>
public static void Register(HttpConfiguration config)
      {
         

            // Web API 路由
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            <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>


      }
<h3>扩展 ActionFilterAttribute</h3>
<blockquote>
<p><font size="3">注意!注意!注意!</font></p>
<p><font size="3">原博文中是扩展的 </font><font style="background-color: #ffff00">AuthorizeAttribute</font>,即认证和授权过滤器,代码实现上是没有多大差别的;在机遇上<strong>认证和授权过滤器</strong>要比方法过滤器实行的要早,更得当做认证和授权的操纵。而我们扩展这个过滤器的目标是对报文举行<strong>署名验证</strong>以及<strong>超时验证</strong>,以是使用<strong>方法过滤器</strong>更适当些。</p></blockquote><font size="3"></font>
<p>下面是扩展过滤器的代码:</p>
<p>
/// <summary>
    /// 扩展方法过滤器,进入方法前验证署名
    /// </summary>
    public class ApiVerifyFilter : ActionFilterAttribute
    {

      public override void OnActionExecuting(HttpActionContext actionContext)
      {
            base.OnActionExecuting(actionContext);

            //获取平台私钥
            string privateKey = Common.GetRsaPrivateKey();

            //获取哀求的超时时间,为了测试设置为100秒,即两次调用隔断不能高出100秒
            string expireyTime = ConfigurationManager.AppSettings["UrlExpireTime"];
            var request = actionContext.Request;

            //验证署名所需header内容
            if (!request.Headers.Contains("signature") || !request.Headers.Contains("timestamp") || !request.Headers.Contains("nonce"))
            {
                SetSpecialResponseMessage(actionContext, 40301);
                return;
            }
            var token = string.Empty;
            var signature = request.Headers.GetValues("signature").FirstOrDefault();
            var timeStamp = request.Headers.GetValues("timestamp").FirstOrDefault();
            var nonce = request.Headers.GetValues("nonce").FirstOrDefault();

            //验证署名
            if (!Common.SignValidate(privateKey, nonce, timeStamp, signature, token))
            {
                SetSpecialResponseMessage(actionContext, 40302);
                return;
            }
            //查抄接口调用是否超时
            var ts = Common.DateTime2TimeStamp(DateTime.UtcNow) - Convert.ToDouble(timeStamp);
            if (ts > int.Parse(expireyTime) * 1000)
            {
                SetSpecialResponseMessage(actionContext, 40303);
                return;
            }
      }

      /// <summary>
      /// 设置署名验证异常返回状态
      /// </summary>
      /// <param name="actionContext">当前哀求上下文</param>
      /// <param name="statusCode">异常状态码</param>
      private static void SetSpecialResponseMessage(HttpActionContext actionContext, int statusCode)
      {
            BizResponseModel model = new BizResponseModel
            {
                Status = statusCode,
                Date = DateTime.Now.ToString("yyyyMMddhhmmssfff"),
                Message = "服务端拒绝访问"
            };
            switch (statusCode)
            {
                case 40301:
                  model.Message = "没有设置署名、时间戳、随机字符串";
                  break;
                case 40302:
                  model.Message = "署名无效";
                  break;
                case 40303:
                  model.Message = "无效的哀求";
                  break;
                default:
                  break;
            }
            actionContext.Response = new HttpResponseMessage
            {
                Content = new StringContent(JsonConvert.SerializeObject(model))
            };
      }


      public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
      {
            base.OnActionExecuted(actionExecutedContext);
      }
    }<br>这里为了方便写了个ResponseModel,代码如下:</p>
/// <summary>
    /// 特殊状态
    /// </summary>
    public class BizResponseModel
    {
      public int Status { get; set; }
      public string Message { get; set; }
      public string Date { get; set; }
    }
<p>然后下面是用的公共方法:</p>
<p>
/// <summary>
      /// 获取时间戳毫秒数
      /// </summary>
      /// <param name="dateTime"></param>
      /// <returns></returns>
      public static long DateTime2TimeStamp(DateTime dateTime)
      {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalMilliseconds);
      }


      public static bool SignValidate(string privateKey, string nonce, string timestamp, string signature, string token)
      {
            bool isValidate = false;
            var tempSign = RsaHelper.RSADecrypt(privateKey, signature);
            string[] arr = new[] { token, timestamp, nonce }.OrderBy(z => z).ToArray();
            string arrString = string.Join("", arr);
            var sha256Result = arrString.EncryptSha256();
            if (sha256Result == tempSign)
            {
                isValidate = true;
            }
            return isValidate;
      }</p>
<p>署名验证的过程如下:</p>
<ol>
<li>获取到报文Header中的 nonce、timestamp、signature、token信息</li>
<li>将token、timestamp、nonce 三者归并数组中,然后举行序次排序(排序为了包管后续三个字符串拼接后一致)</li>
<li>将数组拼接成字符串,然后举行sha256 哈希运算(这里随便什么运算都行,重要为了防止超长加密贫苦)</li>
<li>将上一步的哈希效果与 RSA解密效果举行比对,一致则署名验证通过,否则则署名不一致,哀求为伪造</li></ol>
<p> </p>
<p><br>然后,如今需要启用刚添加的方法过滤器,由于是继承与属性,可以全局启用,大概单个Controller中启用、大概为某个Action启用。全局启用代码如下:</p>
<p>下的<font style="background-color: #ffff00">WebApiConfig</font>类添加如下代码:</p>
config.Filters.Add(new ApiVerifyFilter());
<p><font size="5"></font> </p>
<p><font size="5">OK,全部完成,末了附上两个前后的效果对比!</font></p>
<p><a href="https://img2018.cnblogs.com/blog/171569/201910/171569-20191031190516572-270810520.jpg"></a></p>
<p> </p>
<p>参考博文:</p>
<p><a href="https://www.cnblogs.com/MR-YY/p/5972380.html">WebApi安全性 使用TOKEN+署名验证</a></p>
<p><a href="https://www.cnblogs.com/clly/p/7384008.html">WebAPi接口安全之公钥私钥加密</a></p>
<p><a href="https://www.cnblogs.com/richieyang/p/4918819.html">使用OAuth打造webapi认证服务供自己的客户端使用</a></p>





<p><a href="https://www.cnblogs.com/lijingran/p/6420397.html">Asp.Net WebAPI中Filter过滤器的使用以及实行序次</a></p>
<p><a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">微信 公众号开辟文档</a></p>
<p> </p>
<p>写博文太累了,回家吃螃蟹补补~</p>
页: [1]
查看完整版本: WebApi接口安全性 接口权限调用、参数防篡改防止恶意调用