WebApi實現(xiàn)通訊加密
一. 場景介紹:
如題如何有效的,最少量的現(xiàn)有代碼侵入從而實現(xiàn)客戶端與服務器之間的數(shù)據(jù)交換加密呢?
二. 探究:
1.需求分析
webapi服務端 有如下接口:
public class ApiTestController : ApiController
{
// GET api/<controller>/5
public object Get(int id)
{
return "value" + id;
}
}
ApiTestController
無加密請求
GET /api/apitest?id=10
返回結(jié)果
response "value10"
我們想要達到的效果為:
Get /api/apitest?aWQ9MTA=
response InZhbHVlMTAi (解密所得 "value10")
或者更多其它方式加密
2.功能分析
要想對現(xiàn)有代碼不做任何修改, 我們都知道所有api controller 初始化在router確定之后, 因此我們應在router之前將GET參數(shù)和POST的參數(shù)進行加密才行.
看下圖 webapi 生命周期:

我們看到在 路由routing 之前 有DelegationgHander 層進行消息處理.
因為我們要對每個請求進行參數(shù)解密處理,并且又將返回消息進行加密處理, 因此我們 瞄準 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.
//
// 參數(shù):
// 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.
//
// 參數(shù):
// 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.
//
// 返回結(jié)果:
// 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.
//
// 參數(shù):
// 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.
//
// 返回結(jié)果:
// 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.
//
// 參數(shù):
// 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.
//
// 返回結(jié)果:
// 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
三. 實踐:
現(xiàn)在我們將來 先實現(xiàn)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>
/// 數(shù)據(jù)加解密 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! 到此我們前題工作已經(jīng)完成了80%,開始進行HTTP請求的 消息進和出的加密解密功能的實現(xiàn).
我們暫時將加密的版本信息定義為 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();
// 根據(jù)api_version版本號獲取加密對象, 如果為null 則不需要加密
var encrypt = MessageEncryptionCreator.GetInstance(ver);
if (encrypt != null)
{
// 讀取請求body中的數(shù)據(jù)
string baseContent = request.Content.ReadAsStringAsync().Result;
// 獲取加密的信息
// 兼容 body: 加密數(shù)據(jù) 和 body: code=加密數(shù)據(jù)
baseContent = baseContent.Match("(code=)*(?<code>[\\S]+)", 2);
// URL解碼數(shù)據(jù)
baseContent = baseContent.DecodeUrl();
// 用加密對象解密數(shù)據(jù)
baseContent = encrypt.Decode(baseContent);
string baseQuery = string.Empty;
if (!request.RequestUri.Query.IsNullOrEmpty())
{
// 同 body
// 讀取請求 url query數(shù)據(jù)
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數(shù)據(jù) 重置
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
至此為止功能實現(xiàn)完畢..
四.思想延伸
要想更加安全的方案,可以將給每位用戶生成不同的 private key , 利用AES加密解密
本Demo開源地址:
oschina
https://git.oschina.net/jonneydong/Webapi_Encryption
github
https://github.com/JonneyDong/Webapi_Encryption
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
- NodeJS仿WebApi路由示例
- webapi中如何使用依賴注入
- ASP.NET WebAPi(selfhost)實現(xiàn)文件同步或異步上傳
- jQuery.ajax 跨域請求webapi設置headers的解決方案
- Android4.4 WebAPI實現(xiàn)拍照上傳功能
- WebApi+Bootstrap+KnockoutJs打造單頁面程序
- 在CentOS6.5上使用Jexus安裝部署ASP.NET MVC4和WebApi
- C#進階系列 WebApi身份認證解決方案推薦:Basic基礎認證
- 為ASP.NET MVC及WebApi添加路由優(yōu)先級
- ASP.net WebAPI 上傳圖片實例
相關文章
Asp.net TreeView來構(gòu)建用戶選擇輸入的方法 推薦
選擇優(yōu)于輸入,這是一般人的共識,面對繁多的數(shù)據(jù),提供良好的選擇界面,一方面增強用戶的界面體驗,一方面也提高了數(shù)據(jù)的準確性,更節(jié)省了用戶的寶貴時間。2009-12-12
asp.net post方法中參數(shù)取不出來的解決方法
調(diào)試client端調(diào)用web api的代碼,服務器端的post方法的參數(shù)死活取不出來,下面有個不錯的解決方法,希望對大家有所幫助2014-01-01
Asp.Net Core實現(xiàn)Excel導出功能的實現(xiàn)方法
這篇文章主要給大家介紹了關于Asp.Net Core實現(xiàn)Excel導出功能的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12
TreeNodeCheckChanged事件觸發(fā)方法代碼實例
這篇文章主要介紹了TreeNodeCheckChanged事件觸發(fā)方法代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-12-12

