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     }
View Code

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

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

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

服务端的关键代码包括对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     }
View Code

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

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

二:通过对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     }
View Code

在实现了自定义的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     }
View Code

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

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;
View Code

服务端也是类似的方法,在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     }    
View Code

之后,在(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         }
View Code

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

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

原文地址:https://www.cnblogs.com/you-you-111/p/4915286.html