随着各类设备的兴起,WebApi做为服务也愈来愈流行。而在无任何保护措施的状况下接口彻底暴露在外面,将致使被恶意请求。最近项目的项目中因为提供给APP的接口未对接口进行时间防范致使短信接口被怒对形成必定的损失,临时的措施致使PC和app的防止措施不同致使后来前端调用至关痛苦,选型过oauth,https,固然都被上级未经过,那就只能本身写了,就很,,ԾㅂԾ,,。下面就这次的方式作一次记录。最终的效果:传输过程当中都是密文,别人拿到请求串不能更改请求参数,经过接口过时时间防止同一请求串一直被调用。
前端
不管是APi仍是Mvc请求管道都提供了咱们很好的去扩展,本次说的是api,其实mvc大概意思也是差很少的。咱们如今主要写出大体流程web
从图中能够看出咱们须要在MessageProcessingHandlder上作处理。咱们继承MessageProcessingHandlder重写ProcessRequest和ProcessResponse方法,从方法名能够看出一个是针对请求值处理,一个是针对返回值处理代码以下:json
1 public class CustomerMessageProcesssingHandler : MessageProcessingHandler 2 { 3 protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken) 4 { 5 var contentType = request.Content.Headers.ContentType; 6 7 if (!request.Headers.Contains("platformtype")) 8 { 9 return request; 10 } 11 //根据平台编号得到对应私钥 12 string privateKey = Encoding.UTF8.GetString(Convert.FromBase64String(ConfigurationManager.AppSettings["PlatformPrivateKey_" + request.Headers.GetValues("platformtype").FirstOrDefault()])); 13 if (request.Method == HttpMethod.Post) 14 { 15 // 读取请求body中的数据 16 string baseContent = request.Content.ReadAsStringAsync().Result; 17 // 获取加密的信息 18 // 兼容 body: 加密数据 和 body: sign=加密数据 19 baseContent = Regex.Match(baseContent, "(sign=)*(?<sign>[\\S]+)").Groups[2].Value; 20 // 用加密对象解密数据 21 baseContent = CommonHelper.RSADecrypt(privateKey, baseContent); 22 // 将解密后的BODY数据 重置 23 request.Content = new StringContent(baseContent); 24 //此contentType必须最后设置 不然会变成默认值 25 request.Content.Headers.ContentType = contentType; 26 } 27 if (request.Method == HttpMethod.Get) 28 { 29 string baseQuery = request.RequestUri.Query; 30 // 读取请求 url query数据 31 baseQuery = baseQuery.Substring(1); 32 baseQuery = Regex.Match(baseQuery, "(sign=)*(?<sign>[\\S]+)").Groups[2].Value; 33 baseQuery = CommonHelper.RSADecrypt(privateKey, baseQuery); 34 // 将解密后的 URL 重置URL请求 35 request.RequestUri = new Uri($"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{baseQuery}"); 36 } 37 return request; 38 } 39 protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken) 40 { 41 return response; 42 } 43 }
1 public class CustomRequestAuthorizeAttribute : AuthorizeAttribute 2 { 3 4 public override void OnAuthorization(HttpActionContext actionContext) 5 { 6 //action具备[AllowAnonymous]特性不参与验证 7 if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().OfType<AllowAnonymousAttribute>().Any(x => x is AllowAnonymousAttribute)) 8 { 9 base.OnAuthorization(actionContext); 10 return; 11 } 12 var request = actionContext.Request; 13 string method = request.Method.Method, timeStamp = string.Empty, expireyTime = ConfigurationManager.AppSettings["UrlExpireTime"], timeSign = string.Empty, platformType = string.Empty; 14 if (!request.Headers.Contains("timesign") || !request.Headers.Contains("platformtype") || !request.Headers.Contains("timestamp") || !request.Headers.Contains("expiretime")) 15 { 16 HandleUnauthorizedRequest(actionContext); 17 return; 18 } 19 platformType = request.Headers.GetValues("platformtype").FirstOrDefault(); 20 timeSign = request.Headers.GetValues("timesign").FirstOrDefault(); 21 timeStamp = request.Headers.GetValues("timestamp").FirstOrDefault(); 22 var tempExpireyTime = request.Headers.GetValues("expiretime").FirstOrDefault(); 23 string privateKey = Encoding.UTF8.GetString(Convert.FromBase64String(ConfigurationManager.AppSettings[$"PlatformPrivateKey_{platformType}"])); 24 if (!SignValidate(tempExpireyTime, privateKey, timeStamp, timeSign)) 25 { 26 HandleUnauthorizedRequest(actionContext); 27 return; 28 } 29 if (tempExpireyTime != "0") 30 { 31 expireyTime = tempExpireyTime; 32 } 33 //判断timespan是否有效 34 double ts2 = ConvertHelper.ToDouble((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds, 2), ts = ts2 - ConvertHelper.ToDouble(timeStamp); 35 bool falg = ts > int.Parse(expireyTime) * 1000; 36 if (falg) 37 { 38 HandleUnauthorizedRequest(actionContext); 39 return; 40 } 41 base.IsAuthorized(actionContext); 42 } 43 protected override void HandleUnauthorizedRequest(HttpActionContext filterContext) 44 { 45 base.HandleUnauthorizedRequest(filterContext); 46 47 var response = filterContext.Response = filterContext.Response ?? new HttpResponseMessage(); 48 response.StatusCode = HttpStatusCode.Forbidden; 49 var content = new 50 { 51 BusinessStatus = -10403, 52 StatusMessage = "服务端拒绝访问" 53 }; 54 response.Content = new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json"); 55 } 56 private bool SignValidate(string expiryTime, string privateKey, string timestamp, string sign) 57 { 58 bool isValidate = false; 59 var tempSign = CommonHelper.RSADecrypt(privateKey, sign); 60 if (CommonHelper.EncryptSHA256($"expiretime{expiryTime}" + $"timestamp{timestamp}") == tempSign) 61 { 62 isValidate = true; 63 } 64 return isValidate; 65 } 66 }
请求头部增长参数expiretime使用此参数做为本次接口的过时时间若是没有则表示使用平台默认的接口时间,是咱们能够针对不一样的接口设置不一样的过时时间;timestamp请求时间戳来防止别人拿到接口后一直调用timesign是过时时间和时间戳经过hash而后在经过公钥加密的串来防止别人修改前两个参数。重写HandleUnauthorizedRequest来设置返回内容。api
至此整个验证过程就结束了,咱们在使用过程当中能够创建BaseApi将特性标记上让其余APi继承,固然咱们的接口中可能有的action不须要验证看OnAuthorization第一行代码 增长相应的特性跳过此验证。在整个过程当中其实咱们已经使用了两种加密方式。一是本文中的CustomerMessageProcesssingHandler;另一种就是timestamp+QueryString而后hash 在公钥加密 这样就不须要CustomerMessageProcesssingHandler其实就是本文中的头部加密方式。mvc
本次以HttpClient调用方式为例,展现Get,Post请求加密到执行的相应的action的过程;首先看一下Get请求以下:app
能够看到咱们的请求串url已是密文,头部时间sign也是密文,除非别人拿到咱们的私钥否则是不能修改其参数的。而后请求到达咱们的CustomerMessageProcesssingHandler中咱们看下Get中获得的参数是:ide
这是咱们获得的前端传过来的querystring的参数他的值就是咱们前端加密后传过来的下一步咱们解密应该要获得未加密以前的参数也就是客户端中id=1同时从新给requesturi赋值;post
结果中咱们能够看到id=1已被正确解密获得。接下来进入咱们的CustomRequestAuthorizeAttribute加密
在这一步咱们进行对timeSign的解密对请求只进行hash对比而后验证时间戳是否在过时时间内最终咱们到达相应的action:url
这样整个请求也就完成了Post跟Get区别不大重要的在于拿到传递参数的地方不同这里我只贴一下调用的代码过程同上:
1 public static void PostTestByModel() { 2 3 HttpClient http = new HttpClient(); 4 var timestamp = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds; 5 var expiretime = "600"; 6 var timesign = RSAEncrypt(publicKey, EncryptSHA256($"expiretime{expiretime}timestamp{timestamp}")); 7 var codeValue = RSAEncrypt(publicKey, JsonConvert.SerializeObject(new Tenmp { Id = 1, Name = "cl" })); 8 http.DefaultRequestHeaders.Add("platformtype", "Web"); 9 http.DefaultRequestHeaders.Add("timesign", $"{timesign}"); 10 http.DefaultRequestHeaders.Add("timestamp", $"{string.Format("{0:N2}", timestamp.ToString()) }"); 11 http.DefaultRequestHeaders.Add("expiretime", expiretime); 12 var url1 = string.Format($"{host}api/Values/PostTestByModel"); 13 HttpContent content = new StringContent(codeValue); 14 MediaTypeHeaderValue typeHeader = new MediaTypeHeaderValue("application/json"); 15 typeHeader.CharSet = "UTF-8"; 16 content.Headers.ContentType = typeHeader; 17 var response1 = http.PostAsync(url1, content).Result; 18 }
最后当验证不经过获得的返回值:
这也就是重写HandleUnauthorizedRequest的目的 固然你也能够不重写此方法那么返回的就是401 英文的未经过验证。