WebApi實現通訊加密 (轉)


http://www.cnblogs.com/jonneydong/p/WebApi_Encryption.html

一. 場景介紹:

如題如何有效的,最少量的現有代碼侵入從而實現客戶端與服務器之間的數據交換加密呢?

 

二. 探究:

1.需求分析

webapi服務端 有如下接口:

 

復制代碼
public class ApiTestController : ApiController { // GET api/<controller>/5 public object Get(int id) { return "value" + id; } }
復制代碼

 

無加密請求

 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); }
復制代碼

 

三. 實踐:

現在我們將來 先實現2個版本的通訊加密解密功能,定為 版本1.0 base64加密, 版本1.1 Des加密
 
復制代碼
 1     /// <summary>  2 /// 加密解密接口  3 /// </summary>  4 public interface IMessageEnCryption  5  {  6 /// <summary>  7 /// 加密  8 /// </summary>  9 /// <param name="content"></param> 10 /// <returns></returns> 11 string Encode(string content); 12 /// <summary> 13 /// 解密 14 /// </summary> 15 /// <param name="content"></param> 16 /// <returns></returns> 17 string Decode(string content); 18 }
復制代碼

編寫版本1.0 base64加密解密

復制代碼
 1     /// <summary>  2 /// 加解密 只做 base64  3 /// </summary>  4 public class MessageEncryptionVersion1_0 : IMessageEnCryption  5  {  6 public string Decode(string content)  7  {  8 return content?.DecryptBase64();  9  } 10 11 public string Encode(string content) 12  { 13 return content.EncryptBase64(); 14  } 15 }
復制代碼

編寫版本1.1 des加密解密

復制代碼
 1     /// <summary>  2 /// 數據加解密 des  3 /// </summary>  4 public class MessageEncryptionVersion1_1 : IMessageEnCryption  5  {  6 public static readonly string KEY = "fHil/4]0";  7 public string Decode(string content)  8  {  9 return content.DecryptDES(KEY); 10  } 11 12 public string Encode(string content) 13  { 14 return content.EncryptDES(KEY); 15  } 16 }
復制代碼

 

附上加密解密的基本的一個封裝類

復制代碼
 1     public static class EncrypExtends  2  {  3  4 //默認密鑰向量  5 private static byte[] Keys = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };  6 internal static string Key = "*@&$(@#H";  7  8 //// <summary>  9 /// DES加密字符串 10 /// </summary> 11 /// <param name="encryptString">待加密的字符串</param> 12 /// <param name="encryptKey">加密密鑰,要求為8位</param> 13 /// <returns>加密成功返回加密后的字符串,失敗返回源串</returns> 14 public static string EncryptDES(this string encryptString, string encryptKey) 15  { 16 try 17  { 18 byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey.Substring(0, 8)); 19 byte[] rgbIV = Keys; 20 byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString); 21 DESCryptoServiceProvider dCSP = new DESCryptoServiceProvider(); 22 MemoryStream mStream = new MemoryStream(); 23 CryptoStream cStream = new CryptoStream(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write); 24 cStream.Write(inputByteArray, 0, inputByteArray.Length); 25  cStream.FlushFinalBlock(); 26 return Convert.ToBase64String(mStream.ToArray()); 27  } 28 catch 29  { 30 return encryptString; 31  } 32  } 33 //// <summary> 34 /// DES解密字符串 35 /// </summary> 36 /// <param name="decryptString">待解密的字符串</param> 37 /// <param name="decryptKey">解密密鑰,要求為8位,和加密密鑰相同</param> 38 /// <returns>解密成功返回解密后的字符串,失敗返源串</returns> 39 public static string DecryptDES(this string decryptString, string key) 40  { 41 try 42  { 43 byte[] rgbKey = Encoding.UTF8.GetBytes(key.Substring(0, 8)); 44 byte[] rgbIV = Keys; 45 byte[] inputByteArray = Convert.FromBase64String(decryptString); 46 DESCryptoServiceProvider DCSP = new DESCryptoServiceProvider(); 47 MemoryStream mStream = new MemoryStream(); 48 CryptoStream cStream = new CryptoStream(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write); 49 cStream.Write(inputByteArray, 0, inputByteArray.Length); 50  cStream.FlushFinalBlock(); 51 return Encoding.UTF8.GetString(mStream.ToArray()); 52  } 53 catch 54  { 55 return decryptString; 56  } 57  } 58 public static string EncryptBase64(this string encryptString) 59  { 60 return Convert.ToBase64String(Encoding.UTF8.GetBytes(encryptString)); 61  } 62 public static string DecryptBase64(this string encryptString) 63  { 64 return Encoding.UTF8.GetString(Convert.FromBase64String(encryptString)); 65  } 66 public static string DecodeUrl(this string cryptString) 67  { 68 return System.Web.HttpUtility.UrlDecode(cryptString); 69  } 70 public static string EncodeUrl(this string cryptString) 71  { 72 return System.Web.HttpUtility.UrlEncode(cryptString); 73  } 74 }
復制代碼

 

OK! 到此我們前題工作已經完成了80%,開始進行HTTP請求的 消息進和出的加密解密功能的實現.

我們暫時將加密的版本信息定義為 HTTP header頭中 以 api_version 的value 來判別分別是用何種方式加密解密

header例:

  api_version: 1.0

  api_version: 1.1

 

復制代碼
 1     /// <summary>  2 /// API消息請求處理  3 /// </summary>  4 public class JoyMessageHandler : MessageProcessingHandler  5  {  6  7 /// <summary>  8 /// 接收到request時 處理  9 /// </summary> 10 /// <param name="request"></param> 11 /// <param name="cancellationToken"></param> 12 /// <returns></returns> 13 protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken) 14  { 15 if (request.Content.IsMimeMultipartContent()) 16 return request; 17 // 獲取請求頭中 api_version版本號 18 var ver = System.Web.HttpContext.Current.Request.Headers.GetValues("api_version")?.FirstOrDefault(); 19 // 根據api_version版本號獲取加密對象, 如果為null 則不需要加密 20 var encrypt = MessageEncryptionCreator.GetInstance(ver); 21 22 if (encrypt != null) 23  { 24 // 讀取請求body中的數據 25 string baseContent = request.Content.ReadAsStringAsync().Result; 26 // 獲取加密的信息 27 // 兼容 body: 加密數據 和 body: code=加密數據 28 baseContent = baseContent.Match("(code=)*(?<code>[\\S]+)", 2); 29 // URL解碼數據 30 baseContent = baseContent.DecodeUrl(); 31 // 用加密對象解密數據 32 baseContent = encrypt.Decode(baseContent); 33 34 string baseQuery = string.Empty; 35 if (!request.RequestUri.Query.IsNullOrEmpty()) 36  { 37 // 同 body 38 // 讀取請求 url query數據 39 baseQuery = request.RequestUri.Query.Substring(1); 40 baseQuery = baseQuery.Match("(code=)*(?<code>[\\S]+)", 2); 41 baseQuery = baseQuery.DecodeUrl(); 42 baseQuery = encrypt.Decode(baseQuery); 43  } 44 // 將解密后的 URL 重置URL請求 45 request.RequestUri = new Uri($"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{baseQuery}"); 46 // 將解密后的BODY數據 重置 47 request.Content = new StringContent(baseContent); 48  } 49 50 return request; 51  } 52 53 /// <summary> 54 /// 處理將要向客戶端response時 55 /// </summary> 56 /// <param name="response"></param> 57 /// <param name="cancellationToken"></param> 58 /// <returns></returns> 59 protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken) 60  { 61 //var isMediaType = response.Content.Headers.ContentType.MediaType.Equals(mediaTypeName, StringComparison.OrdinalIgnoreCase); 62 var ver = System.Web.HttpContext.Current.Request.Headers.GetValues("api_version")?.FirstOrDefault(); 63 var encrypt = MessageEncryptionCreator.GetInstance(ver); 64 if (encrypt != null) 65  { 66 if (response.StatusCode == HttpStatusCode.OK) 67  { 68 var result = response.Content.ReadAsStringAsync().Result; 69 // 返回消息 進行加密 70 var encodeResult = encrypt.Encode(result); 71 response.Content = new StringContent(encodeResult); 72  } 73  } 74 75 return response; 76  } 77 78 }
復制代碼

 

最后在 webapiconfig 中將我們的消息處理添加到容器中
 
復制代碼
 1     public static class WebApiConfig  2  {  3 public static void Register(HttpConfiguration config)  4  {  5 // Web API 配置和服務  6 // 將 Web API 配置為僅使用不記名令牌身份驗證。  7  config.SuppressDefaultHostAuthentication();  8 config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));  9 10 // Web API 路由 11  config.MapHttpAttributeRoutes(); 12 13  config.Routes.MapHttpRoute( 14 name: "DefaultApi", 15 routeTemplate: "api/{controller}/{id}", 16 defaults: new { id = RouteParameter.Optional } 17  ); 18 19 // 添加自定義消息處理 20 config.MessageHandlers.Add(new JoyMessageHandler()); 21 22  } 23 }
復制代碼

 

編寫單元測試:
復制代碼
 1  [TestMethod()]  2 public void GetTest()  3  {  4 var id = 10;  5 var resultSuccess = $"\"value{id}\"";  6 //不加密  7 Trace.WriteLine($"without encryption.");  8 var url = $"api/ApiTest?id={id}";  9 Trace.WriteLine($"get url : {url}"); 10 var response = http.GetAsync(url).Result; 11 var result = response.Content.ReadAsStringAsync().Result; 12  Assert.AreEqual(result, resultSuccess); 13 Trace.WriteLine($"result : {result}"); 14 15 //使用 方案1加密 16 Trace.WriteLine($"encryption case one."); 17 18 url = $"api/ApiTest?code=" + $"id={id}".EncryptBase64().EncodeUrl(); 19 20 Trace.WriteLine($"get url : {url}"); 21 22  http.DefaultRequestHeaders.Clear(); 23 http.DefaultRequestHeaders.Add("api_version", "1.0"); 24 response = http.GetAsync(url).Result; 25 26 result = response.Content.ReadAsStringAsync().Result; 27 28 Trace.WriteLine($"result : {result}"); 29 30 result = result.DecryptBase64(); 31 32 Trace.WriteLine($"DecryptBase64 : {result}"); 33 34  Assert.AreEqual(result, resultSuccess); 35 36 //使用 方案2 加密通訊 37 Trace.WriteLine($"encryption case one."); 38 39 url = $"api/ApiTest?code=" + $"id={id}".EncryptDES(MessageEncryptionVersion1_1.KEY).EncodeUrl(); 40 41 Trace.WriteLine($"get url : {url}"); 42 43  http.DefaultRequestHeaders.Clear(); 44 http.DefaultRequestHeaders.Add("api_version", "1.1"); 45 response = http.GetAsync(url).Result; 46 47 result = response.Content.ReadAsStringAsync().Result; 48 49 Trace.WriteLine($"result : {result}"); 50 51 result = result.DecryptDES(MessageEncryptionVersion1_1.KEY); 52 53 Trace.WriteLine($"DecryptBase64 : {result}"); 54 55  Assert.AreEqual(result, resultSuccess); 56 }
復制代碼

 

至此為止功能實現完畢..
 

四.思想延伸

要想更加安全的方案,可以將給每位用戶生成不同的 private key , 利用AES加密解密
 
 
 
 
本Demo開源地址:
oschina
github
 
如須轉載請說明出處
多抽出一分鍾時間學習,讓你的生命更加精彩,敢想敢做,努力做最好!
博客園: JonneyDong 地址:http://www.cnblogs.com/jonneydong/
 
分類: C#
標簽: webapi, c#
好文要頂             關注我     收藏該文         
        
董僑 關注 - 0 粉絲 - 16        
 
 
+加關注    
2    
0    
 
 
 
« 上一篇: .NET 通用高擴展性的細粒度權限管理架構(webApi/Mvc)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM