WebApi服务Uri加密及验证的两种方式
最近的一个项目要求服务端与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加密及验证的两种方式的更多相关文章
- 使用使用dockerfile构建webapi镜像然后使用link和bridge两种方式进行桥接
首先新增一个webapi的项目 项目核心代码 UserContext using Microsoft.EntityFrameworkCore; using System; using System.C ...
- SQL Server验证的两种方式
1.Windows身份验证:本机连接或者受信的局域网连接(一般在忘记管理员密码或者做系统配置的情况下使用). 2.SQLServer验证:使用用户名.密码验证(推荐使用). 启用方法:以Windows ...
- WCF服务使用(IIS+Http)和(Winform宿主+Tcp)两种方式进行发布
1.写在前面 刚接触WCF不久,有很多地方知其然不知其所以然.当我在[创建服务->发布服务->使用服务]这一过程出现过许多问题.如客户端找不到服务引用:客户端只在本机环境中才能访问服务,移 ...
- 服务容错保护断路器Hystrix之一:入门示例介绍(springcloud引入Hystrix的两种方式)
限流知识<高可用服务设计之二:Rate limiting 限流与降级> 在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元间通过服务注册与订阅的方式互相依赖.由于每个单元都在不同的 ...
- 不停止MySQL服务增加从库的两种方式
不停止MySQL服务增加从库的两种方式 转载自:http://lizhenliang.blog.51cto.com/7876557/1669829 现在生产环境MySQL数据库是一主一从,由于业务量访 ...
- XFire构建服务端Service的两种方式(转)
XFire构建服务端service的两种方式,一是用xfire构建,二是和spring集成构建. 一,xifre构建,确保把xfire的jar包导入到工程中或classpath. 1,service的 ...
- XFire构建服务端Service的两种方式
1.原声构建: 2.集成spring构建 http://blog.csdn.net/carefree31441/article/details/4000436XFire构建服务端Service的两种方 ...
- 不停止MySQL服务增加从库的两种方式【转载】
现在生产环境MySQL数据库是一主一从,由于业务量访问不断增大,故再增加一台从库.前提是不能影响线上业务使用,也就是说不能重启MySQL服务,为了避免出现其他情况,选择在网站访问量低峰期时间段操作. ...
- springboot 注册服务注册中心(zk)的两种方式
在使用springboot进行开发的过程中,我们经常需要处理这样的场景:在服务启动的时候,需要向服务注册中心(例如zk)注册服务状态,以便当服务状态改变的时候,可以故障摘除和负载均衡. 我遇到过两种注 ...
随机推荐
- [No000032]程序员的年龄天花板
程序员职业生涯中流行这这样一个定律:35岁定律,那35岁以上的老程序员都干吗去了呢?为了讨论程序员的职业寿命,我们先得给公司或者团队分分类.大概有这么三类: 外包型 项目型 产品型 咱们一一来说一下吧 ...
- jquery中取消和绑定hover事件的正确方式
在网页设计中,我们经常使用jquery去响应鼠标的hover事件,和mouseover和mouseout事件有相同的效果,但是这其中其中如何使用bind去绑定hover方法呢?如何用unbind取消绑 ...
- 网络/运维工程师visio2013模具图标 绘制漂亮的网络拓扑图 狮子XL工程师美学思想
visio2013狮子XL自定义运维模具下载: 链接:http://pan.baidu.com/s/1bo779Kz 密码:xh3s 狮子XL 的美学思想: 1,一次痛苦,一生幸福. 之前,在绘制网络 ...
- Linux 退格键不回显
在程序使用system("stty erase ^H");可以实现在输入状态下,按退格键删除字符,不回显. 调用tcsetattr修改linux基本输入的控制字符定义 //Linu ...
- 轻量级iOS蓝牙库:LGBluetooth(项目中用过的)
LGBluetooth 是个简单的,基于块的,轻量级 CoreBluetooth 库: iOS 6引入了Core Bluetooth,让低功耗蓝牙设备之间的通信变得简单.但如果CoreBluetoot ...
- js从0开始构思表情插件
前言: 由于公司开发项目需要用到表情插件,在网上百度了好久,很多表情插件,都是需要引用好多js文件,也没有现成的demo可以使用,还有一些插件是引用好多图片,每一个表情都要重新请求一下.为了这样一个功 ...
- FineUI小技巧(3)表格导出与文件下载
需求描述 实际应用中,我们可能需要导出表格内容,或者在页面回发时根据用户权限下载文件(注意,这里的导出与下载,都是在后台进行的,和普通的一个链接下载文件不同). 点击按钮导出表格 由于FineUI 默 ...
- 【语言基础】c++ 基本数据类型与字节数组(string,char [] )之间的转化方法
有时候我们需要将基本数据类型转化为字节,以便写入文件,然后必要时还需要将这些字节读出来.有人说,为啥不把数字直接存进文件呢?比如:100,000,000,我们直接存数字明文到文件那就是9个字符(cha ...
- Replace Pioneer注册
以下是目前合法长期使用Replace Pioneer的唯一方法(除了购买之外): Replace Pioneer过期后,会弹出一个注册(Registration)窗口,其中有一个试用选项(Trial ...
- [POJ1284]Primitive Roots(原根性质的应用)
题目:http://poj.org/problem?id=1284 题意:就是求一个奇素数有多少个原根 分析: 使得方程a^x=1(mod m)成立的最小正整数x是φ(m),则称a是m的一个原根 然后 ...