這不是變態(tài)的想法, 這只是對(duì)現(xiàn)實(shí)需求的轉(zhuǎn)化.
因?yàn)橛忻芪? 所以本文不適用于瀏覽器到服務(wù)端的數(shù)據(jù)交換;
只適用于服務(wù)端到服務(wù)端的數(shù)據(jù)傳輸.
用傳統(tǒng)的方法對(duì)輸入輸出做加解密, 無非就是在入口處做操作. 但是 WebApi 的參數(shù)如果接收的是一串加密字符串, 那基本上等于和 WebApi 強(qiáng)大的模型綁定 Say baybay 了.
要加解密, 又想利用 WebApi 的便利, 有沒有什么好的方法呢? 用 ActionFilter ? ModelBinder ?? 好像不能很好的解決(沒有仔細(xì)的研究).
參考了 Microsoft.AspNet.WebApi.MessageHandlers.Compression 的寫法, 我寫了個(gè)簡(jiǎn)單的實(shí)現(xiàn)..
將返回結(jié)果加密
聲明 ActionFilter
用以指示后續(xù)的處理程序, 哪些Action結(jié)果是要密的; 如果需要加密輸出, 則在 Response 的 Header 的 ContentType 里加上 encrypt 參數(shù)
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class EncryptAttribute : ActionFilterAttribute
{
public bool Ignore { get; set; }
public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
{
await base.OnActionExecutedAsync(actionExecutedContext, cancellationToken);
if (!this.Ignore)
{
actionExecutedContext.Response.Content.Headers.ContentType.Parameters.Add(new System.Net.Http.Headers.NameValueHeaderValue("encrypt", ""));
}
}
}
屬性: Ignore , 如果值為 true , 則告訴處理程序, 結(jié)果不需要加密;
注意 AllowMutltiple 一定是 false, 避免 Controller 和 Action 上的 Filter 交叉.
使用 EncryptAttribute
[Encrypt]
public class TestController : ApiController
{
public Test Get()
{
return new Test()
{
ID = 1,
Name = "xling"
};
}
[Encrypt(Ignore = true)]
public Test Post(Test test)
{
return test;
}
}
派生 DelegatingHandler
重寫 SendAsync 方法
public class CryptoHandler : DelegatingHandler
{
...
...
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
return await this.HandleResponse(request, response, cancellationToken);
}
...
...
private async Task<HttpResponseMessage> HandleResponse(HttpRequestMessage request, HttpResponseMessage response, CancellationToken cancellationToken)
{
if (response.Content != null && response.Content.Headers.ContentType.Parameters.Any(p => string.Equals(p.Name, "encrypt", StringComparison.OrdinalIgnoreCase)))
{
var inputBytes = await response.Content.ReadAsByteArrayAsync();
var encryptByte = AesHelper.Encrypt(inputBytes, KEY);
var base64 = Convert.ToBase64String(encryptByte);
var encryptedContent = new StringContent(base64);
encryptedContent.Headers.Clear();
response.Content.Headers.CopyTo(encryptedContent.Headers);
response.Content = encryptedContent;
return response;
}
return response;
}
}
在 HandleResponse 方法里, 首先判斷 Response Header 的 ContentType 里是否包含 encrypt 這個(gè)參數(shù).
跟據(jù)生命周期, 這里的 encrypt 就是上面的 ActionFilter 寫進(jìn)來的.
緊接著就是把返回內(nèi)容當(dāng)作字符串加密,并轉(zhuǎn)化為 Base64 格式, 寫進(jìn) StringContent 里.
然后把原始的 Response Header 覆蓋到新的 StringContent 里去.
使用 CryptoHandler
修改 Global 的 Application_Start 方法, 在最后面加上:
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new CryptoHandler());
看一下輸出的 Response Header
解密收到的請(qǐng)求
對(duì)CryptoHandler擴(kuò)展
上面的 CryptoHandler 只對(duì) Response 做了處理. 這里我們要修改 SendAsync 方法, 使它能夠?qū)魅氲募用軘?shù)據(jù)還原成可以被 WebApi 的 ModelBinder 識(shí)別的數(shù)據(jù).
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request = await this.HandleRequest(request, cancellationToken);
var response = await base.SendAsync(request, cancellationToken);
return await this.HandleResponse(request, response, cancellationToken);
}
private async Task<HttpRequestMessage> HandleRequest(HttpRequestMessage request, CancellationToken cancellation)
{
if (request.Content?.Headers.ContentType?.Parameters?.Any(y => string.Equals(y.Name, "encrypt", StringComparison.OrdinalIgnoreCase)) == true)
{
var input = await request.Content.ReadAsStringAsync();
var inputBytes = Convert.FromBase64String(input);
var decryptedBytes = AesHelper.Decrypt(inputBytes, KEY);
//var str = Encoding.UTF8.GetString(decryptedBytes);
var stm = new MemoryStream(decryptedBytes);
var decryptedContent = new StreamContent(stm);
request.Content.Headers.CopyTo(decryptedContent.Headers);
request.Content = decryptedContent;
return request;
}
return request;
}
跟加密一樣, 解密的第一步也是判斷 ContentType 里是否包含參數(shù) encrypt.
接著就是把請(qǐng)求的內(nèi)容按 string 取出, 并用 base64 解碼(因?yàn)樯弦徊疆a(chǎn)生的結(jié)果, 我們用 base64 轉(zhuǎn)義了.)
然后對(duì)結(jié)果解密, 并寫入 StreamContent, 替換 request 的 Content.
在運(yùn)行下去, 就到 Action 里去了.
看一下請(qǐng)求示例
提交數(shù)據(jù)的時(shí)候, 必須告訴 Content-Type , 加密之前是什么格式的, 而且還要帶上 encrypt .
上圖示例, 我提交的數(shù)據(jù)在加密之前是 xml 數(shù)據(jù).
其它
CryptoHandler.cs 完整代碼
using XXX.Common;
using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Extensions.Compression.Core.Extensions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace XXX.XXX.XXX
{
public class CryptoHandler : DelegatingHandler
{
private static string KEY = "FF545E10-EDB8-4086-861C-AADFAED015C3";
public static void Init(string key)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(key);
KEY = key;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request = await this.HandleRequest(request, cancellationToken);
var response = await base.SendAsync(request, cancellationToken);
return await this.HandleResponse(request, response, cancellationToken);
}
private async Task<HttpRequestMessage> HandleRequest(HttpRequestMessage request, CancellationToken cancellation)
{
if (request.Content?.Headers.ContentType?.Parameters?.Any(y => string.Equals(y.Name, "encrypt", StringComparison.OrdinalIgnoreCase)) == true)
{
var input = await request.Content.ReadAsStringAsync();
var inputBytes = Convert.FromBase64String(input);
var decryptedBytes = AesHelper.Decrypt(inputBytes, KEY);
//var str = Encoding.UTF8.GetString(decryptedBytes);
var stm = new MemoryStream(decryptedBytes);
var decryptedContent = new StreamContent(stm);
request.Content.Headers.CopyTo(decryptedContent.Headers);
request.Content = decryptedContent;
return request;
}
return request;
}
private async Task<HttpResponseMessage> HandleResponse(HttpRequestMessage request, HttpResponseMessage response, CancellationToken cancellationToken)
{
if (response.Content != null && response.Content.Headers.ContentType.Parameters.Any(p => string.Equals(p.Name, "encrypt", StringComparison.OrdinalIgnoreCase)))
{
var inputBytes = await response.Content.ReadAsByteArrayAsync();
var encryptByte = AesHelper.Encrypt(inputBytes, KEY);
var base64 = Convert.ToBase64String(encryptByte);
var encryptedContent = new StringContent(base64);
encryptedContent.Headers.Clear();
response.Content.Headers.CopyTo(encryptedContent.Headers);
response.Content = encryptedContent;
return response;
}
return response;
}
}
}