最近的一个项目要求服务端与UI层分离,业务层以WebApi方式向外提供所有业务服务,服务在数据保密性方面提出了要求,主要包括:

1:客户端认证;

2:服务请求超时(默认5分钟);

3:服务Get请求的参数密文传输。

以上三个需求为一般的网络服务比较常见的简单要求,在WebApi项目中也比较容易实现,以下是我采用的两种实现方式(任意一种即可):

一:继承HttpClient来加入数据(Uri)加密,加入验证Header,继承ApiController来对Header进行合法性验证,并解密Uri

客户端的关键代码包括对HttpClient继承实现:

     public class MyHttpClient : HttpClient
     {
         private static MyHttpClient _myClient;

         public static MyHttpClient Instance
         {
             get
             {
                 if(_myClient!=null)return _myClient;
                 _myClient = new MyHttpClient();
                 _myClient.DefaultRequestHeaders.Add("Mac", MacMd5);
                 return _myClient;
             }
         }

         private MyHttpClient()
         {
         }

         private static readonly IAuthorize _authorize = new MacAuthorize();
         private static string _macMd5 = string.Empty;

         private static string MacMd5
         {
             get
             {
                 if (!string.IsNullOrEmpty(_macMd5)) return _macMd5;
                 var macAddress = _authorize.UniqueId;
                 if (string.IsNullOrEmpty(macAddress)) return string.Empty;
                 _macMd5 = macAddress.GetMD5();
                 return _macMd5;
             }
         }

         private static string TimeAes
         {
             get
             {
                 var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                 var aesTime = time.AesEncrypt();
                 return aesTime;
             }
         }

         private static void AddHeader(ref string url)
         {
             var segs = url.Split('/');
             ];
             if (para.Contains("="))
             {
                 var aes = para.AesEncrypt();
                 url = url.Replace(para, aes.GetMD5());
                 IEnumerable<string> p = new List<string>();
                 if (_myClient.DefaultRequestHeaders.TryGetValues("Param", out p))
                     _myClient.DefaultRequestHeaders.Remove("Param");
                 _myClient.DefaultRequestHeaders.Add("Param",aes);
             }

             IEnumerable<string> time = new List<string>();
             if (_myClient.DefaultRequestHeaders.TryGetValues("Time", out time))
                 _myClient.DefaultRequestHeaders.Remove("Time");
             _myClient.DefaultRequestHeaders.Add("Time", TimeAes);
         }

         public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
             System.Threading.CancellationToken cancellationToken)
         {
             var url = request.RequestUri.AbsoluteUri;
             AddHeader(ref url);
             return base.SendAsync(request, cancellationToken);
         }

         public override int GetHashCode()
         {
             return _myClient.GetHashCode();
         }

     }

其中时间戳为了简便,未转为ms格式进行验证,仅作为字符串进行了验证。

在客户端进行调用时,通过继承的MyHttpClient发送服务请求即可:

 var client = MyHttpClient.Instance;
 var response = client.GetAsync(urlBase.AbsoluteUri + "api/test/GetUser/id=123123&name=jiakai").Result;

服务端的关键代码包括对ApiController的继承实现:

     public class BaseApiController : ApiController
     {
         protected override void Initialize(HttpControllerContext controllerContext)
         {
             base.Initialize(controllerContext);
             //获取请求头信息
             var requestHeader = controllerContext.Request.Headers;
             IEnumerable<string> mac = new List<string>();
             requestHeader.TryGetValues("Mac", out mac);
             IEnumerable<string> time = new List<string>();
             requestHeader.TryGetValues("Time", out time);
             IEnumerable<string> para = new List<string>();
             requestHeader.TryGetValues("Param", out para);
             var paramList = controllerContext.Request.RequestUri.AbsoluteUri.Split('/');
             if (mac == null || time == null || para == null)
             {
                 var resp = Request.CreateResponse<string>(HttpStatusCode.Forbidden, "非法请求", "application/json");
                 throw new HttpResponseException(resp);
             }
             //验证机器MAC地址
             if (mac.Any()&&!string.IsNullOrEmpty(mac.First()))
             {
                 //TODO:验证机器MAC地址是否为已注册MAC地址
             }
             //验证时间戳
             if (time.Any() && !string.IsNullOrEmpty(time.First()))
             {
                 var t = Convert.ToDateTime(time.First().AesDecrypt());
                 ) < DateTime.Now)
                 {
                     var resp = Request.CreateResponse<string>(HttpStatusCode.RequestTimeout, "请求过期", "application/json");
                     throw new HttpResponseException(resp);
                 }
             }
             //验证参数MD5
             if (para.Any() && !string.IsNullOrEmpty(para.First()))
             {
                 ];
                 if (para.First().GetMD5() != newMd5)
                 {
                     var resp = Request.CreateResponse<string>(HttpStatusCode.Forbidden, "参数MD5验证失败", "application/json");
                     throw new HttpResponseException(resp);
                 }
             }
         }
     }

同样,在业务实现的Controller中,继承改为BaseApiController即可:

 public class TestController : BaseApiController
 {
     //TODO:业务服务具体实现
 }

二:通过对WebApi消息处理框架中HttpMessageHandler的自定义实现,并分别注入到Request及Response的消息处理阶段,来实现加密/解密的功能

客户端HttpClient中HttpMessageHandler的实现:

     public class RequestHandler : DelegatingHandler
     {
         private static string TimeAes
         {
             get
             {
                 var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                 var aesTime = time.AesEncrypt();
                 return aesTime;
             }
         }

         protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
             System.Threading.CancellationToken cancellationToken)
         {
             var uri = request.RequestUri.AbsoluteUri;
             var port = request.RequestUri.Port;
             var para = uri.Split('/').Last();
             if (para.Contains("="))
             {
                 var aes = para.AesEncrypt();
                 uri = uri.Replace(para, aes.GetMD5());
                 IEnumerable<string> p = new List<string>();
                 if (request.Headers.TryGetValues("Param", out p))
                     request.Headers.Remove("Param");
                 request.Headers.Add("Param", aes);
             }
             IEnumerable<string> time = new List<string>();
             if (request.Headers.TryGetValues("Time", out time))
                 request.Headers.Remove("Time");
             request.Headers.Add("Time", TimeAes);
             request.RequestUri = new Uri(uri);

             return base.SendAsync(request, cancellationToken);
         }
     }

在实现了自定义的HttpMessageHandler后,实现一个HttpClient工厂,对外提供一个注入了该HttpMessageHandler的HttpClient单例对象:

     public class AtmHttpClientFactory
     {
         private static HttpClient _atmClient;
         private readonly static HttpClientHandler ClientHandler = new HttpClientHandler();

         public static HttpClient GetClient()
         {
             if (_atmClient != null) return _atmClient;
             _atmClient = new HttpClient(new RequestHandler() {InnerHandler = ClientHandler });
             _atmClient.DefaultRequestHeaders.Add("Mac", MacMd5);
             return _atmClient;
         }

         private AtmHttpClientFactory()
         {
         }

         private static readonly IAuthorize _authorize = new MacAuthorize();
         private static string _macMd5 = String.Empty;

         private static string MacMd5
         {
             get
             {
                 if (!String.IsNullOrEmpty(_macMd5)) return _macMd5;
                 var macAddress = _authorize.UniqueId;
                 if (String.IsNullOrEmpty(macAddress)) return String.Empty;
                 _macMd5 = macAddress.GetMD5();
                 return _macMd5;
             }
         }
     }

这样,在客户端的调用加密过程就完全被封装,简化为如下:

      //调用实现1
      var myClient = AtmHttpClientFactory.GetClient();
      var myResponse = myClient.GetAsync("http://api.ezbatm.com:7777/api/test/GetUser/id=123123&name=jiakai").Result;

服务端也是类似的方法,在app的config中注入我们自己实现的服务端HttpMessageHandler即可。response的HttpMessageHandler实现:

     public class ResponseHandler : DelegatingHandler
     {
         protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
             System.Threading.CancellationToken cancellationToken)
         {
             //获取请求头信息
             IEnumerable<string> mac = new List<string>();
             request.Headers.TryGetValues("Mac", out mac);
             IEnumerable<string> time = new List<string>();
             request.Headers.TryGetValues("Time", out time);
             IEnumerable<string> para = new List<string>();
             request.Headers.TryGetValues("Param", out para);
             var paramList = request.RequestUri.AbsoluteUri.Split('/');
             //验证Header是否完整
             if (mac == null || time == null || para == null)
             {
                 return RequestNotAcceptable();
             }
             //验证机器MAC地址
             if (mac.Any() && !string.IsNullOrEmpty(mac.First()))
             {
                 //TODO:验证机器MAC地址是否为已注册MAC地址
             }
             //验证时间戳
             if (time.Any() && !string.IsNullOrEmpty(time.First()))
             {
                 var t = Convert.ToDateTime(time.First().AesDecrypt());
                 ) < DateTime.Now)
                 {
                     return RequestTimeOut();
                 }
             }
             //验证参数MD5
             if (para.Any() && !string.IsNullOrEmpty(para.First()))
             {
                 ];
                 //if (para.First().GetMD5() != newMd5)
                 {
                     return RequestForbidden();
                 }
             }
             return base.SendAsync(request, cancellationToken);
         }

         private static Task<HttpResponseMessage> RequestForbidden()
         {
             return Task.Factory.StartNew(() =>
             {
                 return new HttpResponseMessage(HttpStatusCode.Forbidden)
                 {
                     Content = new StringContent("请求未通过认证")
                 };
             });
         }

         private static Task<HttpResponseMessage> RequestTimeOut()
         {
             return Task.Factory.StartNew(() =>
             {
                 return new HttpResponseMessage(HttpStatusCode.RequestTimeout)
                 {
                     Content = new StringContent("请求已超时")
                 };
             });
         }

         private static Task<HttpResponseMessage> RequestNotAcceptable()
         {
             return Task.Factory.StartNew(() =>
             {
                 return new HttpResponseMessage(HttpStatusCode.NotAcceptable)
                 {
                     Content = new StringContent("请求为非法请求")
                 };
             });
         }
     }    

之后,在(owin框架)Startup中注入该自定义HttpMessageHandler即可:

         public void Configuration(IAppBuilder app)
         {
             // Configure Web API for self-host.
             var config = new HttpConfiguration();
             config.Routes.MapHttpRoute(
                 name: "DefaultApi",
                 routeTemplate: "api/{controller}/{action}/{param}",
                 defaults: new { id = RouteParameter.Optional }
             );
             //注入response handler
             config.MessageHandlers.Add(new ResponseHandler());

             app.UseWebApi(config);
         }

完成了以上客户端+服务器端的工作后,即可按照我们的实际需求对Request及Response消息进行自定义的处理。

总结:以上两种方法中,客户端及服务器端的方案都是解耦的,也就是是,你可以采用方法一的客户端方案+方法二的服务器端方案,等等以此类推。

WebApi服务Uri加密及验证的两种方式的更多相关文章

  1. 使用使用dockerfile构建webapi镜像然后使用link和bridge两种方式进行桥接

    首先新增一个webapi的项目 项目核心代码 UserContext using Microsoft.EntityFrameworkCore; using System; using System.C ...

  2. SQL Server验证的两种方式

    1.Windows身份验证:本机连接或者受信的局域网连接(一般在忘记管理员密码或者做系统配置的情况下使用). 2.SQLServer验证:使用用户名.密码验证(推荐使用). 启用方法:以Windows ...

  3. WCF服务使用(IIS+Http)和(Winform宿主+Tcp)两种方式进行发布

    1.写在前面 刚接触WCF不久,有很多地方知其然不知其所以然.当我在[创建服务->发布服务->使用服务]这一过程出现过许多问题.如客户端找不到服务引用:客户端只在本机环境中才能访问服务,移 ...

  4. 服务容错保护断路器Hystrix之一:入门示例介绍(springcloud引入Hystrix的两种方式)

    限流知识<高可用服务设计之二:Rate limiting 限流与降级> 在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式互相依赖.由于每个单元都在不同的 ...

  5. 不停止MySQL服务增加从库的两种方式

    不停止MySQL服务增加从库的两种方式 转载自:http://lizhenliang.blog.51cto.com/7876557/1669829 现在生产环境MySQL数据库是一主一从,由于业务量访 ...

  6. XFire构建服务端Service的两种方式(转)

    XFire构建服务端service的两种方式,一是用xfire构建,二是和spring集成构建. 一,xifre构建,确保把xfire的jar包导入到工程中或classpath. 1,service的 ...

  7. XFire构建服务端Service的两种方式

    1.原声构建: 2.集成spring构建 http://blog.csdn.net/carefree31441/article/details/4000436XFire构建服务端Service的两种方 ...

  8. 不停止MySQL服务增加从库的两种方式【转载】

    现在生产环境MySQL数据库是一主一从,由于业务量访问不断增大,故再增加一台从库.前提是不能影响线上业务使用,也就是说不能重启MySQL服务,为了避免出现其他情况,选择在网站访问量低峰期时间段操作. ...

  9. springboot 注册服务注册中心(zk)的两种方式

    在使用springboot进行开发的过程中,我们经常需要处理这样的场景:在服务启动的时候,需要向服务注册中心(例如zk)注册服务状态,以便当服务状态改变的时候,可以故障摘除和负载均衡. 我遇到过两种注 ...

随机推荐

  1. 第10章 同步设备I/O和异步设备I/O(1)_常见设备及CreateFile函数

    10.1 打开和关闭设备 10.1.1 设备的定义——在Windows中可以与之进行通信的任何东西. (1)常见设备及用途 设备 用途 用来打开设备的函数 文件 永久存储任何数据 CreateFile ...

  2. pitch yaw roll 的区别

    http://blog.163.com/vipwdp@126/blog/static/150224366201281935518196/

  3. [No000058]一口气读完一本英语书

    个人:"如何学好英语?"99个人会回答:"去,读英文原著." 然而这事儿吧,恐怕比坚持背单词还难.无论少侠们背过多少单词,最后都会败在生词太多.句子太难的坎儿上 ...

  4. domReady方法(dom加载完成执行回调)

    var domReady = function( fn ) { var isReady = false, ready = function(){ if(!isReady){ typeof fn === ...

  5. css相关问题

    display:none和visibility:hidden的区别? 前几天遇到的这个问题,表格布局:::::display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢 ...

  6. JS中将JSON的字符串解析成JSON数据格式《转》

    在JS中将JSON的字符串解析成JSON数据格式,一般有两种方式: 1.一种为使用eval()函数. 2. 使用Function对象来进行返回解析. 使用eval函数来解析,并且使用jquery的ea ...

  7. PAT 1010. 一元多项式求导 (25)

    设计函数求一元多项式的导数.(注:xn(n为整数)的一阶导数为n*xn-1.) 输入格式:以指数递降方式输入多项式非零项系数和指数(绝对值均为不超过1000的整数).数字间以空格分隔. 输出格式:以与 ...

  8. [py]shell着色

    print "\033[32;1myou are 30 older and little than 40\033[0m"

  9. 283 Move Zeroes

    /** * 题意:将0挪到末尾,并且不改数组中原有元素的顺序 * 解析:找到0元素,然后寻找其后面非0的元素,进行交换位置 * @param {number[]} nums * @return {vo ...

  10. DEV winform treelist设置背景图像

    treelist是一个复杂的控件,包括选中行,奇偶行等均可以单独设置显示效果,空白区域上背景图像的代码如下: private void treeList1_CustomDrawEmptyArea(ob ...