一. 场景介绍:
如题如何有效的,最少量的现有代码侵入从而实现客户端与服务器之间的数据交换加密呢?
二. 探究:
1.需求分析
webapi服务端 有如下接口:
public class ApiTestController : ApiController { // GET api/<controller>/5 public object Get(int id) { return "value" + id; } } ApiTestController
无加密请求
GET /api/apitest?id=10
返回结果
response "value10"
我们想要达到的效果为:
Get /api/apitest?aWQ9MTA=
response InZhbHVlMTAi (解密所得 "value10")
或者更多其它方式加密
2.功能分析
要想对现有代码不做任何修改, 我们都知道所有api controller 初始化在router确定之后, 因此我们应在router之前将GET参数和POST的参数进行加密才行.
看下图 webapi 生命周期:
我们看到在 路由routing 之前 有DelegationgHander 层进行消息处理.
因为我们要对每个请求进行参数解密处理,并且又将返回消息进行加密处理, 因此我们 瞄准 MessageProcessingHandler
// // 摘要: // A base type for handlers which only do some small processing of request and/or // response messages. public abstract class MessageProcessingHandler : DelegatingHandler { // // 摘要: // Creates an instance of a System.Net.Http.MessageProcessingHandler class. protected MessageProcessingHandler(); // // 摘要: // Creates an instance of a System.Net.Http.MessageProcessingHandler class with // a specific inner handler. // // 参数: // innerHandler: // The inner handler which is responsible for processing the HTTP response messages. protected MessageProcessingHandler(HttpMessageHandler innerHandler); // // 摘要: // Performs processing on each request sent to the server. // // 参数: // request: // The HTTP request message to process. // // cancellationToken: // A cancellation token that can be used by other objects or threads to receive // notice of cancellation. // // 返回结果: // Returns System.Net.Http.HttpRequestMessage.The HTTP request message that was // processed. protected abstract HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken); // // 摘要: // Perform processing on each response from the server. // // 参数: // response: // The HTTP response message to process. // // cancellationToken: // A cancellation token that can be used by other objects or threads to receive // notice of cancellation. // // 返回结果: // Returns System.Net.Http.HttpResponseMessage.The HTTP response message that was // processed. protected abstract HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken); // // 摘要: // Sends an HTTP request to the inner handler to send to the server as an asynchronous // operation. // // 参数: // request: // The HTTP request message to send to the server. // // cancellationToken: // A cancellation token that can be used by other objects or threads to receive // notice of cancellation. // // 返回结果: // Returns System.Threading.Tasks.Task`1.The task object representing the asynchronous // operation. // // 异常: // T:System.ArgumentNullException: // The request was null. protected internal sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); } MessageProcessingHandler
三. 实践:
现在我们将来 先实现2个版本的通讯加密解密功能,定为 版本1.0 base64加密, 版本1.1 Des加密
/// <summary> /// 加密解密接口 /// </summary> public interface IMessageEnCryption { /// <summary> /// 加密 /// </summary> /// <param name="content"></param> /// <returns></returns> string Encode(string content); /// <summary> /// 解密 /// </summary> /// <param name="content"></param> /// <returns></returns> string Decode(string content); } IMessageEnCryption
编写版本1.0 base64加密解密
/// <summary> /// 加解密 只做 base64 /// </summary> public class MessageEncryptionVersion1_0 : IMessageEnCryption { public string Decode(string content) { return content?.DecryptBase64(); } public string Encode(string content) { return content.EncryptBase64(); } } MessageEncryptionVersion1_0
编写版本1.1 des加密解密
/// <summary> /// 数据加解密 des /// </summary> public class MessageEncryptionVersion1_1 : IMessageEnCryption { public static readonly string KEY = "fHil/4]0"; public string Decode(string content) { return content.DecryptDES(KEY); } public string Encode(string content) { return content.EncryptDES(KEY); } } MessageEncryptionVersion1_1
附上加密解密的基本的一个封装类
public static class EncrypExtends { //默认密钥向量 private static byte[] Keys = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF }; internal static string Key = "*@&$(@#H"; //// <summary> /// DES加密字符串 /// </summary> /// <param name="encryptString">待加密的字符串</param> /// <param name="encryptKey">加密密钥,要求为8位</param> /// <returns>加密成功返回加密后的字符串,失败返回源串</returns> public static string EncryptDES(this string encryptString, string encryptKey) { try { byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8)); byte[] rgbIV = Keys; byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString); DESCryptoServiceProvider dCSP = new DESCryptoServiceProvider(); MemoryStream mStream = new MemoryStream(); CryptoStream cStream = new CryptoStream(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write); cStream.Write(inputByteArray, 0, inputByteArray.Length); cStream.FlushFinalBlock(); return Convert.ToBase64String(mStream.ToArray()); } catch { return encryptString; } } //// <summary> /// DES解密字符串 /// </summary> /// <param name="decryptString">待解密的字符串</param> /// <param name="decryptKey">解密密钥,要求为8位,和加密密钥相同</param> /// <returns>解密成功返回解密后的字符串,失败返源串</returns> public static string DecryptDES(this string decryptString, string key) { try { byte[] rgbKey = Encoding.UTF8.GetBytes(key.Substring(0, 8)); byte[] rgbIV = Keys; byte[] inputByteArray = Convert.FromBase64String(decryptString); DESCryptoServiceProvider DCSP = new DESCryptoServiceProvider(); MemoryStream mStream = new MemoryStream(); CryptoStream cStream = new CryptoStream(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write); cStream.Write(inputByteArray, 0, inputByteArray.Length); cStream.FlushFinalBlock(); return Encoding.UTF8.GetString(mStream.ToArray()); } catch { return decryptString; } } public static string EncryptBase64(this string encryptString) { return Convert.ToBase64String(Encoding.UTF8.GetBytes(encryptString)); } public static string DecryptBase64(this string encryptString) { return Encoding.UTF8.GetString(Convert.FromBase64String(encryptString)); } public static string DecodeUrl(this string cryptString) { return System.Web.HttpUtility.UrlDecode(cryptString); } public static string EncodeUrl(this string cryptString) { return System.Web.HttpUtility.UrlEncode(cryptString); } } EncrypExtends
OK! 到此我们前题工作已经完成了80%,开始进行HTTP请求的 消息进和出的加密解密功能的实现.
我们暂时将加密的版本信息定义为 HTTP header头中 以 api_version 的value 来判别分别是用何种方式加密解密
header例:
api_version: 1.0
api_version: 1.1
/// <summary> /// API消息请求处理 /// </summary> public class JoyMessageHandler : MessageProcessingHandler { /// <summary> /// 接收到request时 处理 /// </summary> /// <param name="request"></param> /// <param name="cancellationToken"></param> /// <returns></returns> protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Content.IsMimeMultipartContent()) return request; // 获取请求头中 api_version版本号 var ver = System.Web.HttpContext.Current.Request.Headers.GetValues("api_version")?.FirstOrDefault(); // 根据api_version版本号获取加密对象, 如果为null 则不需要加密 var encrypt = MessageEncryptionCreator.GetInstance(ver); if (encrypt != null) { // 读取请求body中的数据 string baseContent = request.Content.ReadAsStringAsync().Result; // 获取加密的信息 // 兼容 body: 加密数据 和 body: code=加密数据 baseContent = baseContent.Match("(code=)*(?<code>[\\S]+)", 2); // URL解码数据 baseContent = baseContent.DecodeUrl(); // 用加密对象解密数据 baseContent = encrypt.Decode(baseContent); string baseQuery = string.Empty; if (!request.RequestUri.Query.IsNullOrEmpty()) { // 同 body // 读取请求 url query数据 baseQuery = request.RequestUri.Query.Substring(1); baseQuery = baseQuery.Match("(code=)*(?<code>[\\S]+)", 2); baseQuery = baseQuery.DecodeUrl(); baseQuery = encrypt.Decode(baseQuery); } // 将解密后的 URL 重置URL请求 request.RequestUri = new Uri($"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{baseQuery}"); // 将解密后的BODY数据 重置 request.Content = new StringContent(baseContent); } return request; } /// <summary> /// 处理将要向客户端response时 /// </summary> /// <param name="response"></param> /// <param name="cancellationToken"></param> /// <returns></returns> protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken) { //var isMediaType = response.Content.Headers.ContentType.MediaType.Equals(mediaTypeName, StringComparison.OrdinalIgnoreCase); var ver = System.Web.HttpContext.Current.Request.Headers.GetValues("api_version")?.FirstOrDefault(); var encrypt = MessageEncryptionCreator.GetInstance(ver); if (encrypt != null) { if (response.StatusCode == HttpStatusCode.OK) { var result = response.Content.ReadAsStringAsync().Result; // 返回消息 进行加密 var encodeResult = encrypt.Encode(result); response.Content = new StringContent(encodeResult); } } return response; } } JoyMessageHandler
最后在 webapiconfig 中将我们的消息处理添加到容器中
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服务 // 将 Web API 配置为仅使用不记名令牌身份验证。 config.SuppressDefaultHostAuthentication(); config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // 添加自定义消息处理 config.MessageHandlers.Add(new JoyMessageHandler()); } } WebApiConfig
编写单元测试:
[TestMethod()] public void GetTest() { var id = 10; var resultSuccess = $"\"value{id}\""; //不加密 Trace.WriteLine($"without encryption."); var url = $"api/ApiTest?id={id}"; Trace.WriteLine($"get url : {url}"); var response = http.GetAsync(url).Result; var result = response.Content.ReadAsStringAsync().Result; Assert.AreEqual(result, resultSuccess); Trace.WriteLine($"result : {result}"); //使用 方案1加密 Trace.WriteLine($"encryption case one."); url = $"api/ApiTest?code=" + $"id={id}".EncryptBase64().EncodeUrl(); Trace.WriteLine($"get url : {url}"); http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Add("api_version", "1.0"); response = http.GetAsync(url).Result; result = response.Content.ReadAsStringAsync().Result; Trace.WriteLine($"result : {result}"); result = result.DecryptBase64(); Trace.WriteLine($"DecryptBase64 : {result}"); Assert.AreEqual(result, resultSuccess); //使用 方案2 加密通讯 Trace.WriteLine($"encryption case one."); url = $"api/ApiTest?code=" + $"id={id}".EncryptDES(MessageEncryptionVersion1_1.KEY).EncodeUrl(); Trace.WriteLine($"get url : {url}"); http.DefaultRequestHeaders.Clear(); http.DefaultRequestHeaders.Add("api_version", "1.1"); response = http.GetAsync(url).Result; result = response.Content.ReadAsStringAsync().Result; Trace.WriteLine($"result : {result}"); result = result.DecryptDES(MessageEncryptionVersion1_1.KEY); Trace.WriteLine($"DecryptBase64 : {result}"); Assert.AreEqual(result, resultSuccess); } ApiTestControllerTests
至此为止功能实现完毕..
四.思想延伸
要想更加安全的方案,可以将给每位用户生成不同的 private key , 利用AES加密解密
本Demo开源地址:
oschina
https://git.oschina.net/jonneydong/Webapi_Encryption
github
https://github.com/JonneyDong/Webapi_Encryption
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持小牛知识库!
本文向大家介绍Android实现通讯录功能,包括了Android实现通讯录功能的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了Android通讯录案例,供大家参考,具体内容如下 实战演练——通讯录 1、功能描述:通过SQLite实现数据库的增删改查 2、技术要点:SQLite的基本操作 3、实现步骤: ① 创建一个类继承SQLiteOpenHelper ② 重写父类构造方法、onCr
本文向大家介绍Nodejs+Socket.io实现通讯实例代码,包括了Nodejs+Socket.io实现通讯实例代码的使用技巧和注意事项,需要的朋友参考一下 目录结构 需要的条件 socket.io.js 供前端界面初始化io socket.io 供NodeJs端提供socket方法 socket.io.js存在于socket.io-client socket.io存在于socket.io 演示
本文向大家介绍PHP实现Soap通讯的方法,包括了PHP实现Soap通讯的方法的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了PHP实现Soap通讯的方法。分享给大家供大家参考。具体实现方法如下: 希望本文所述对大家的PHP程序设计有所帮助。
本文向大家介绍Python实现命令行通讯录实例教程,包括了Python实现命令行通讯录实例教程的使用技巧和注意事项,需要的朋友参考一下 1、实现目标 编写一个命令行通讯录程序,可以添加、查询、删除通讯录好友及电话 2、实现方法 创建一个类来表示一个人的信息。使用字典存储每个人的对象,名字作为键。 使用pickle模块永久地把这些对象存储下来。 使用字典内建的方法添加、删除修改人员信息。 3、思维导
本文向大家介绍python实现简易通讯录修改版,包括了python实现简易通讯录修改版的使用技巧和注意事项,需要的朋友参考一下 描述: 上一篇博客我写了一个简单的通讯录,但是还是觉得不够完美: 需要输入ID,虽然ID是主键,但是没有实现自增功能; 忘记加电话号码了; 如果插入用户名相同,则后续的查、改、删功能受到影响; 所以,我这个修改版主要修正了以上三点缺陷。具体如下 将用户ID设置为自增字段,
本文向大家介绍Android4.4 WebAPI实现拍照上传功能,包括了Android4.4 WebAPI实现拍照上传功能的使用技巧和注意事项,需要的朋友参考一下 网上有很多关于拍照上传的实现方法,如果用新版本android去运行有可能会发现根本实现不了。主要原因是android从4.4版本开始通过intent.ACTION_GET_CONTENT打开选择器后,getData()返回的URI没有包