WebApi服务Uri加密及验证的两种方式

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

1:客户端认证;

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

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

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

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

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

 1     public class MyHttpClient : HttpClient
 2     {
 3         private static MyHttpClient _myClient;
 4
 5         public static MyHttpClient Instance
 6         {
 7             get
 8             {
 9                 if(_myClient!=null)return _myClient;
10                 _myClient = new MyHttpClient();
11                 _myClient.DefaultRequestHeaders.Add("Mac", MacMd5);
12                 return _myClient;
13             }
14         }
15
16         private MyHttpClient()
17         {
18         }
19
20         private static readonly IAuthorize _authorize = new MacAuthorize();
21         private static string _macMd5 = string.Empty;
22
23         private static string MacMd5
24         {
25             get
26             {
27                 if (!string.IsNullOrEmpty(_macMd5)) return _macMd5;
28                 var macAddress = _authorize.UniqueId;
29                 if (string.IsNullOrEmpty(macAddress)) return string.Empty;
30                 _macMd5 = macAddress.GetMD5();
31                 return _macMd5;
32             }
33         }
34
35         private static string TimeAes
36         {
37             get
38             {
39                 var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
40                 var aesTime = time.AesEncrypt();
41                 return aesTime;
42             }
43         }
44
45         private static void AddHeader(ref string url)
46         {
47             var segs = url.Split(‘/‘);
48             var para = segs[segs.Length - 1];
49             if (para.Contains("="))
50             {
51                 var aes = para.AesEncrypt();
52                 url = url.Replace(para, aes.GetMD5());
53                 IEnumerable<string> p = new List<string>();
54                 if (_myClient.DefaultRequestHeaders.TryGetValues("Param", out p))
55                     _myClient.DefaultRequestHeaders.Remove("Param");
56                 _myClient.DefaultRequestHeaders.Add("Param",aes);
57             }
58
59             IEnumerable<string> time = new List<string>();
60             if (_myClient.DefaultRequestHeaders.TryGetValues("Time", out time))
61                 _myClient.DefaultRequestHeaders.Remove("Time");
62             _myClient.DefaultRequestHeaders.Add("Time", TimeAes);
63         }
64
65         public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
66             System.Threading.CancellationToken cancellationToken)
67         {
68             var url = request.RequestUri.AbsoluteUri;
69             AddHeader(ref url);
70             return base.SendAsync(request, cancellationToken);
71         }
72
73         public override int GetHashCode()
74         {
75             return _myClient.GetHashCode();
76         }
77
78     }

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

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

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

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

 1     public class BaseApiController : ApiController
 2     {
 3         protected override void Initialize(HttpControllerContext controllerContext)
 4         {
 5             base.Initialize(controllerContext);
 6             //获取请求头信息
 7             var requestHeader = controllerContext.Request.Headers;
 8             IEnumerable<string> mac = new List<string>();
 9             requestHeader.TryGetValues("Mac", out mac);
10             IEnumerable<string> time = new List<string>();
11             requestHeader.TryGetValues("Time", out time);
12             IEnumerable<string> para = new List<string>();
13             requestHeader.TryGetValues("Param", out para);
14             var paramList = controllerContext.Request.RequestUri.AbsoluteUri.Split(‘/‘);
15             if (mac == null || time == null || para == null)
16             {
17                 var resp = Request.CreateResponse<string>(HttpStatusCode.Forbidden, "非法请求", "application/json");
18                 throw new HttpResponseException(resp);
19             }
20             //验证机器MAC地址
21             if (mac.Any()&&!string.IsNullOrEmpty(mac.First()))
22             {
23                 //TODO:验证机器MAC地址是否为已注册MAC地址
24             }
25             //验证时间戳
26             if (time.Any() && !string.IsNullOrEmpty(time.First()))
27             {
28                 var t = Convert.ToDateTime(time.First().AesDecrypt());
29                 if (t.AddMinutes(5) < DateTime.Now)
30                 {
31                     var resp = Request.CreateResponse<string>(HttpStatusCode.RequestTimeout, "请求过期", "application/json");
32                     throw new HttpResponseException(resp);
33                 }
34             }
35             //验证参数MD5
36             if (para.Any() && !string.IsNullOrEmpty(para.First()))
37             {
38                 var newMd5 = paramList[paramList.Length - 1];
39                 if (para.First().GetMD5() != newMd5)
40                 {
41                     var resp = Request.CreateResponse<string>(HttpStatusCode.Forbidden, "参数MD5验证失败", "application/json");
42                     throw new HttpResponseException(resp);
43                 }
44             }
45         }
46     }

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

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

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

客户端HttpClient中HttpMessageHandler的实现:

 1     public class RequestHandler : DelegatingHandler
 2     {
 3         private static string TimeAes
 4         {
 5             get
 6             {
 7                 var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
 8                 var aesTime = time.AesEncrypt();
 9                 return aesTime;
10             }
11         }
12
13         protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
14             System.Threading.CancellationToken cancellationToken)
15         {
16             var uri = request.RequestUri.AbsoluteUri;
17             var port = request.RequestUri.Port;
18             var para = uri.Split(‘/‘).Last();
19             if (para.Contains("="))
20             {
21                 var aes = para.AesEncrypt();
22                 uri = uri.Replace(para, aes.GetMD5());
23                 IEnumerable<string> p = new List<string>();
24                 if (request.Headers.TryGetValues("Param", out p))
25                     request.Headers.Remove("Param");
26                 request.Headers.Add("Param", aes);
27             }
28             IEnumerable<string> time = new List<string>();
29             if (request.Headers.TryGetValues("Time", out time))
30                 request.Headers.Remove("Time");
31             request.Headers.Add("Time", TimeAes);
32             request.RequestUri = new Uri(uri);
33
34             return base.SendAsync(request, cancellationToken);
35         }
36     }

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

 1     public class AtmHttpClientFactory
 2     {
 3         private static HttpClient _atmClient;
 4         private readonly static HttpClientHandler ClientHandler = new HttpClientHandler();
 5
 6         public static HttpClient GetClient()
 7         {
 8             if (_atmClient != null) return _atmClient;
 9             _atmClient = new HttpClient(new RequestHandler() {InnerHandler = ClientHandler });
10             _atmClient.DefaultRequestHeaders.Add("Mac", MacMd5);
11             return _atmClient;
12         }
13
14         private AtmHttpClientFactory()
15         {
16         }
17
18         private static readonly IAuthorize _authorize = new MacAuthorize();
19         private static string _macMd5 = String.Empty;
20
21         private static string MacMd5
22         {
23             get
24             {
25                 if (!String.IsNullOrEmpty(_macMd5)) return _macMd5;
26                 var macAddress = _authorize.UniqueId;
27                 if (String.IsNullOrEmpty(macAddress)) return String.Empty;
28                 _macMd5 = macAddress.GetMD5();
29                 return _macMd5;
30             }
31         }
32     }

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

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

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

 1     public class ResponseHandler : DelegatingHandler
 2     {
 3         protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
 4             System.Threading.CancellationToken cancellationToken)
 5         {
 6             //获取请求头信息
 7             IEnumerable<string> mac = new List<string>();
 8             request.Headers.TryGetValues("Mac", out mac);
 9             IEnumerable<string> time = new List<string>();
10             request.Headers.TryGetValues("Time", out time);
11             IEnumerable<string> para = new List<string>();
12             request.Headers.TryGetValues("Param", out para);
13             var paramList = request.RequestUri.AbsoluteUri.Split(‘/‘);
14             //验证Header是否完整
15             if (mac == null || time == null || para == null)
16             {
17                 return RequestNotAcceptable();
18             }
19             //验证机器MAC地址
20             if (mac.Any() && !string.IsNullOrEmpty(mac.First()))
21             {
22                 //TODO:验证机器MAC地址是否为已注册MAC地址
23             }
24             //验证时间戳
25             if (time.Any() && !string.IsNullOrEmpty(time.First()))
26             {
27                 var t = Convert.ToDateTime(time.First().AesDecrypt());
28                 if (t.AddMinutes(5) < DateTime.Now)
29                 {
30                     return RequestTimeOut();
31                 }
32             }
33             //验证参数MD5
34             if (para.Any() && !string.IsNullOrEmpty(para.First()))
35             {
36                 var newMd5 = paramList[paramList.Length - 1];
37                 //if (para.First().GetMD5() != newMd5)
38                 {
39                     return RequestForbidden();
40                 }
41             }
42             return base.SendAsync(request, cancellationToken);
43         }
44
45         private static Task<HttpResponseMessage> RequestForbidden()
46         {
47             return Task.Factory.StartNew(() =>
48             {
49                 return new HttpResponseMessage(HttpStatusCode.Forbidden)
50                 {
51                     Content = new StringContent("请求未通过认证")
52                 };
53             });
54         }
55
56         private static Task<HttpResponseMessage> RequestTimeOut()
57         {
58             return Task.Factory.StartNew(() =>
59             {
60                 return new HttpResponseMessage(HttpStatusCode.RequestTimeout)
61                 {
62                     Content = new StringContent("请求已超时")
63                 };
64             });
65         }
66
67         private static Task<HttpResponseMessage> RequestNotAcceptable()
68         {
69             return Task.Factory.StartNew(() =>
70             {
71                 return new HttpResponseMessage(HttpStatusCode.NotAcceptable)
72                 {
73                     Content = new StringContent("请求为非法请求")
74                 };
75             });
76         }
77     }    

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

 1         public void Configuration(IAppBuilder app)
 2         {
 3             // Configure Web API for self-host.
 4             var config = new HttpConfiguration();
 5             config.Routes.MapHttpRoute(
 6                 name: "DefaultApi",
 7                 routeTemplate: "api/{controller}/{action}/{param}",
 8                 defaults: new { id = RouteParameter.Optional }
 9             );
10             //注入response handler
11             config.MessageHandlers.Add(new ResponseHandler());
12
13             app.UseWebApi(config);
14         }

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

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

时间: 2024-10-25 00:56:19

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

SQL Server验证的两种方式

1.Windows身份验证:本机连接或者受信的局域网连接(一般在忘记管理员密码或者做系统配置的情况下使用). 2.SQLServer验证:使用用户名.密码验证(推荐使用). 启用方法:以Windows身份验证进去,然后右键根节点—>安全性. 3.sa用户:SQLServer的最高权限管理员账户,启用方法:根节点—>安全性—>sa—>常规中修改密码,默认的是强制复杂密码,可以取消“强制实施密码策略”.“状态”的“登陆”勾选为“启用”. 4.基于安全考虑,不要启用sa账户,而是针对数据

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

1.写在前面 刚接触WCF不久,有很多地方知其然不知其所以然.当我在[创建服务->发布服务->使用服务]这一过程出现过许多问题.如客户端找不到服务引用:客户端只在本机环境中才能访问服务,移植到其他机器上就不能访问服务(权限问题)等问题.所以写下这篇文章把我使用http和tcp这两方式部署服务所出现的问题以及解决方案记录下来,方便自己下次查看,也可以当作初学WCF的一个入门小示例吧. 2.建立一个WCF服务 首先要编写一个WCF服务,我在这里提供一个通过名字查询年龄的服务,代码如下: 服务契约:

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

转载自:http://lizhenliang.blog.51cto.com/7876557/1669829 现在生产环境MySQL数据库是一主一从,由于业务量访问不断增大,故再增加一台从库.前提是不能影响线上业务使用,也就是说不能重启MySQL服务,为了避免出现其他情况,选择在网站访问量低峰期时间段操作. 一般在线增加从库有两种方式,一种是通过mysqldump备份主库,恢复到从库,mysqldump是逻辑备份,数据量大时,备份速度会很慢,锁表的时间也会很长.另一种是通过xtrabackup工具

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

不停止 MySQL 服务增加从库的两种方式 提交 我的评论 加载中 已评论 不停止 MySQL 服务增加从库的两种方式 2015-07-12 数据库开发 数据库开发 数据库开发 微信号 DBDevs 功能介绍 分享数据库相关技术文章.教程和工具,另外还包括数据库相关的工作.偶尔也谈谈程序员人生 :) (点击上方蓝字,快速关注我们) 作者:李振良 网址:http://lizhenliang.blog.51cto.com/7876557/1669829 现在生产环境MySQL数据库是一主一从,由于业

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

现在生产环境MySQL数据库是一主一从,由于业务量访问不断增大,故再增加一台从库.前提是不能影响线上业务使用,也就是说不能重启MySQL服务,为了避免出现其他情况,选择在网站访问量低峰期时间段操作. 一般在线增加从库有两种方式,一种是通过mysqldump备份主库,恢复到从库,mysqldump是逻辑备份,数据量大时,备份速度会很慢,锁表的时间也会很长.另一种是通过xtrabackup工具备份主库,恢复到从库,xtrabackup是物理备份,备份速度快,不锁表.为什么不锁表?因为自身会监控主库日

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

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

不停MySQL服务情况下增加从库两种常用方式

现在生产环境MySQL数据库是一主一从,由于业务量访问不断增大,故再增加一台从库.前提是不能影响线上业务使用,也就是说不能重启MySQL服务,为了避免出现其他情况,选择在网站访问量低峰期时间段操作. 一般在线增加从库有两种方式,一种是通过mysqldump备份主库,恢复到从库,mysqldump是逻辑备份,数据量大时,备份速度会很慢,锁表的时间也会很长.另一种是通过xtrabackup工具备份主库,恢复到从库,xtrabackup是物理备份,备份速度快,不锁表.为什么不锁表?因为自身会监控主库日

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

在使用springboot进行开发的过程中,我们经常需要处理这样的场景:在服务启动的时候,需要向服务注册中心(例如zk)注册服务状态,以便当服务状态改变的时候,可以故障摘除和负载均衡. 我遇到过两种注册的途径: 1.在Spring的webapplication启动完成后,直接进行注册: 2.在servlet容器启动完成后,通过listener进行注册. 本文通过一个demo讲述一下这两种注册方式,使用的是传统的向zk注册的方案. 1.Spring webapplication启动完成后注册 先上

springcloud 服务调用的两种方式

spring-cloud调用服务有两种方式,一种是Ribbon+RestTemplate, 另外一种是Feign.Ribbon是一个基于HTTP和TCP客户端的负载均衡器,其实feign也使用了ribbon, 只要使用@FeignClient时,ribbon就会自动使用. 一.Ribbon 1.1新建模块client-apom文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="