背景先容 迩来使用WebApi开辟一套对外接口,重要是数据的外送以及效果回传,接口没什么难度,接纳WebApi+EF的架构简朴创建一个模板工程,使用template天生一套WebApi接口,去掉put、delete等操纵,修改一下就可以上线。这些都不在话下,反正网上一大堆教程,随便找谁人step by step做下来就可以了。 然后发布上线后,接口是放在外网,面对两个题目: - 怎样包管接口的调用的正当性
- 怎样包管接口及数据的安全性
其实这两个题目是相互团结的,先包管正当,然后在正当底子上包管哀求的唯一性,制止参数被篡改。 鉴于接口上线限期紧迫,团结浩繁案例,先管理掉接口调用数据的安全性题目,这里接纳了RSA报文加解密的方案,包管数据安全和防止接口被恶意调用以及参数篡改的题目。 本文参考博客园多篇博文,内容多有引用,文末附有参照博文的地址。 以下为正文! 正文 起首,接口面对的题目: 哀求泉源(身份)是否正当(部门管理,后续在处置惩罚)? - 哀求参数被篡改?
- 哀求的唯一性(不可复制),防止哀求被恶意攻击
管理方案: - 参数加密: 客户端和服务端参数接纳RSA加密后通报,原则上只有持有私钥的服务端才气解密客户端公钥加密的参数,制止了参数篡改的题目
- 哀求署名:接纳一套署名算法,对哀求举行署名验证,包管哀求的唯一性
这里参照了WebAPi使用公钥私钥加密先容和使用 一文,举行公钥私钥加解密的处置惩罚 先说服务端: 扩展 MessageProcessingHandler 先看一下MessageProcessingHandler的先容: - #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);
- }
- }
复制代码
扩展这个类的目标是解密参数,其实也可以推迟到Action过滤器中做,但是照旧以为机遇上在这里处置惩罚比力符合。具体的发起相识一下WebApi消息管道以及扩展过滤器的干系文章,本文不再延伸。
下面是扩展的实今世码:
- /// <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[2].Value;
- baseQuery = RsaHelper.RSADecrypt(privateKey, baseQuery);
- var requestUrl = $"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{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[2].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;
- }
- }
复制代码
获取平台私钥那里,现实上可以针对差别的接口调用方单独一个,另起一篇在先容。
然后找到管理方案【App_Start】目次下的WebApiConfig类,在内里添加如下代码,启用消息处置惩罚扩展类: - 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>
- }
复制代码扩展 ActionFilterAttribute
注意!注意!注意!
原博文中是扩展的 AuthorizeAttribute,即认证和授权过滤器,代码实现上是没有多大差别的;在机遇上认证和授权过滤器要比方法过滤器实行的要早,更得当做认证和授权的操纵。而我们扩展这个过滤器的目标是对报文举行署名验证以及超时验证,以是使用方法过滤器更适当些。
下面是扩展过滤器的代码:
- /// <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);
- }
- }
复制代码 这里为了方便写了个ResponseModel,代码如下:- /// <summary>
- /// 特殊状态
- /// </summary>
- public class BizResponseModel
- {
- public int Status { get; set; }
- public string Message { get; set; }
- public string Date { get; set; }
- }
复制代码然后下面是用的公共方法:
- /// <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;
- }
复制代码
署名验证的过程如下:
- 获取到报文Header中的 nonce、timestamp、signature、token信息
- 将token、timestamp、nonce 三者归并数组中,然后举行序次排序(排序为了包管后续三个字符串拼接后一致)
- 将数组拼接成字符串,然后举行sha256 哈希运算(这里随便什么运算都行,重要为了防止超长加密贫苦)
- 将上一步的哈希效果与[signature] RSA解密效果举行比对,一致则署名验证通过,否则则署名不一致,哀求为伪造
然后,如今需要启用刚添加的方法过滤器,由于是继承与属性,可以全局启用,大概单个Controller中启用、大概为某个Action启用。全局启用代码如下:
下的WebApiConfig类添加如下代码: - config.Filters.Add(new ApiVerifyFilter());
复制代码
OK,全部完成,末了附上两个前后的效果对比!
参考博文:
WebApi安全性 使用TOKEN+署名验证
WebAPi接口安全之公钥私钥加密
使用OAuth打造webapi认证服务供自己的客户端使用
Asp.Net WebAPI中Filter过滤器的使用以及实行序次
微信 公众号开辟文档
写博文太累了,回家吃螃蟹补补~ |