WebApi 如何 優(yōu)雅的 對(duì) 輸入輸出 解密加密

這不是變態(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


加密輸出 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)求示例

加密提交 以 raw 方式提交數(shù)據(jù)

加密提交 Request Header

提交數(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;
        }

    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逆巍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌顶霞,老刑警劉巖图焰,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咐蚯,死亡現(xiàn)場(chǎng)離奇詭異饵骨,居然都是意外死亡回溺,警方通過查閱死者的電腦和手機(jī)悦荒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門唯欣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搬味,你說我怎么就攤上這事境氢。” “怎么了碰纬?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵萍聊,是天一觀的道長。 經(jīng)常有香客問我悦析,道長寿桨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任强戴,我火速辦了婚禮亭螟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘酌泰。我一直安慰自己媒佣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布陵刹。 她就那樣靜靜地躺著默伍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衰琐。 梳的紋絲不亂的頭發(fā)上也糊,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音羡宙,去河邊找鬼狸剃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛狗热,可吹牛的內(nèi)容都是我干的钞馁。 我是一名探鬼主播虑省,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼僧凰!你這毒婦竟也來了探颈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤训措,失蹤者是張志新(化名)和其女友劉穎伪节,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绩鸣,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡怀大,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呀闻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片化借。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖总珠,靈堂內(nèi)的尸體忽然破棺而出屏鳍,到底是詐尸還是另有隱情勘纯,我是刑警寧澤局服,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站驳遵,受9級(jí)特大地震影響淫奔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜堤结,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一唆迁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竞穷,春花似錦唐责、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至看政,卻和暖如春朴恳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背允蚣。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工于颖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嚷兔。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓森渐,卻偏偏與公主長得像做入,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子同衣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理母蛛,服務(wù)發(fā)現(xiàn),斷路器乳怎,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • 概述 之前一直對(duì)加密相關(guān)的算法知之甚少彩郊,只知道類似DES、RSA等加密算法能對(duì)數(shù)據(jù)傳輸進(jìn)行加密蚪缀,且各種加密算法各有...
    Henryzhu閱讀 3,019評(píng)論 0 14
  • 文/小葉 寫得較為真實(shí)但狭,請(qǐng)勿對(duì)號(hào)入座碘饼,以免受傷。我的劇本我做主,黑桃三苦囱,要不起…… 2016.7.12 接上回:書...
    博土閱讀 407評(píng)論 5 2
  • 向榜樣學(xué)習(xí)! 今天的早讀帶給大家關(guān)于榜樣的力量荤懂。 分段來看吃靠。 reinvent v. 改革,改良 | reinve...
    然媽Miya閱讀 547評(píng)論 0 2
  • 聽著《我要你》寫寫《驢得水》觀后感。 我?guī)缀醪粫?huì)花錢去影院看國產(chǎn)的2D電影护桦,破例看了的含衔,要么反響好,要么我本身就相...
    白茶時(shí)光閱讀 456評(píng)論 0 0