WebAPi之SelfHost自建立證書啓動Https疑難解惑及沒法正確返回結果

前言

話說又來需求了,以前對於在SelfHost中須要嵌套頁面並操做爲非正常需求,這回來正常需求了,客戶端如今加了https,老大過來講WebAPi訪問不了了,這是什麼狀況,我去試了試,還真是這個狀況,不知道如何下手啊,最終爲了解決這個問題,漫長的探索之旅就這樣開始了,但願給須要在SelfHost下啓動Https的童鞋一點啓示和幫助。前端

話題引入

當在客戶端發送請求發送不過去時看了看谷歌控制檯顯示的消息大意是不能混合使用即客戶端爲https,則WebAPi不能爲http,估計是爲了安全的問題,因而乎問題就比較明朗了,只須要將WebAPi修改爲https便可,直接將SelfHost中地址修改成Https顯然是不行的必需要藉助證書並開啓對應的端口才行,既然咱們已經分析完畢,接下來咱們解決WebAPi啓動Https的問題便可。開始想到去申請的一個免費的證書就行,可是因爲是將WebAPi安裝在本地Windows服務中且ip不固定,因此免費的證書則再也不可取,只能自行建立證書來解決這個問題。可是我對於證書幾乎從未接觸過,一無所知,不知從哪裏下手,這一漫長的過程就今後開始。web

解決SelfHost啓動Https

在園中搜索有關自建立證書的答案,基本要麼是IIS,要麼是WCF建立證書,不過仍是有了一點思路,提供了自建立證書須要的命令,開始有一點思緒。當看到以下一條命令時我要驚呆了,難道就這樣解決了嗎,so  easy!chrome

第一次嘗試

http add urlacl url=https://+:port/ user=""

結果發現這只是url的保留項而已。shell

第二次嘗試

咱們經過VS開發命令來建立證書 json

第一步:windows

MakeCert -sv d:DevelopmentCA.pvk -n "CN=WebAPi CA" d:DevelopmentCA.cer -b 09/20/2016 -e 12/31/2050 -r

【注意】:該命令最後一個-r不能缺乏,-r是建立自建立證書的標識,不然當利用Pvk2pfx建立私鑰時則會出現【ERROR: File not found.(Error Code = 0x80070002)】api

結果變成以下:跨域

第二步:數組

打開MMC將建立的證書導入到受信任的頒發證書機構中,則變成以下這樣:安全

第三步:

拿到該證書的指紋並監聽ip以及端口,經過以下命令進行:

http add sslcert ipport=0.0.0.0:8084 certhash=‎996645BAB7169F2AFD7599A696DA2586862843C6 appid={41992502-E5D4-4794-BB01-D4A7414480CC}

一切都是如此的完美,最後看下結果,再次讓我大失所望:

此時我已經處在崩潰的邊緣,搜索這個緣由的答案,都沒法解決,此時只能求助萬能的stackoverflow,看到這個答案使人驚喜一番:

http://stackoverflow.com/questions/13076915/ssl-certificate-add-failed-when-binding-to-port

大意是將自建立的證書要經過MMC首先導入到【我的】證書中才行,因而乎將其拖到我的證書中看看。

結果依然是這個錯誤,當回過來再看上述全部回答答案時,下面一個回答簡直是拯救了我,這個問題困擾我一天,讓我無比激動。請看這句話:

You can easily check if your certificate has private a key as so: mmc - certificates - local machinepersonal. Look at the icon of the certificate - it MUST have key sign on the icon.

還要在本地計算中的我的證書中看自建立的證書左上角是否有個小鑰匙,這個鑰匙也就是私鑰,咱們還得建立私鑰才行,經過以下命令進行。

第四步:

建立證書的私鑰

Pvk2pfx -pvk DevelopmentCA.pvk -spc DevelopmentCA.cer -pfx DevelopmentSSL.pfx -po password

接下來咱們再來運行第三步則成功以下:

咱們接下來運行程序https:localhost:8084來驗收成果,結果以下:

那麼這個問題又該如何解決呢?可能有些人就得說了,點擊下面的高級直接前往不就能夠了嗎,雖然這樣式能夠接受,可是在咱們項目中,是經過【設備】去訪問WebAPi,基於這點絕對不可行,so must kill it。當我各類嘗試後發現這樣作卻能夠。咱們將 MakeCert -sv d:DevelopmentCA.pvk -n "CN=WebAPi CA" d:DevelopmentCA.cer -b 09/20/2016 -e 12/31/2050 -r 中的CN修改成localhost即頒發給localhost時卻能夠,重複性的動做則再也不演示,給演示最終結果以下:

注意:當到這裏時若是你仍是發現證書是無效時請進行以下操做,參考資料來源於搜索時來自於youtube的一段視頻演示,下面請看:

將演示最後中【容許將標識符用於受保護內容(可能須要從新重啓計算機)】去掉便可。

到了這裏關於在WebAPi之SelfHost啓動Https的問題基本上解決了一大半,對於我來講,對於你來講可能已經Over,可是在實際場景中卻還沒完成。當在測試時發現如上壓根不會請求到WebAPi,此時在谷歌控制檯卻出現以下的錯誤:

::Net_Error_Response

第三次嘗試

解決客戶端沒法訪問localhost。

百思不得其解,搜索資料時覺得是跨域的問題,結果卻不是,在客戶端那邊是用的WebAPi的本地IP來訪問,因此想一想是否是localhost不行呢。本想偷點懶,在Hosts文件夾裏對localhost進行映射,結果依然不行,因而乎訪問地址變成:https:192.168.3.6:8084最終完事。

到了這裏也就完成了90%,我的還不是很知足,尋思着雖然有一點是繞不過去,那就是首先得將證書導入【受信任的頒發機構中】,可是還須要經過MMC將證書導入我的證書,這一點是我沒法接受,我繼續開始探索之旅。經過代碼的形式將證書導入到MMC的【我的】證書中。 

第四次嘗試

經過代碼形式將證書導入到【我的】證書中,而非經過MMC導入尋求解決方案。

經過運行以下代碼來嘗試建立訪問https證書:

            var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx";
            var cert = new X509Certificate2(fileName, "password");
            var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.ReadWrite);
            store.Add(cert);
            store.Close();


            Process p = new Process();
            p.StartInfo.FileName = "netsh.exe";
            p.StartInfo.Arguments = string.Format("http add sslcert ipport=0.0.0.0:8084 certhash={0} appid={1}", cert.GetCertHashString(), "{" + "41992502-E5D4-4794-BB01-D4A7414480CC"+"}");
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;            
            p.Start();
            p.Close();

此時經過以下命令來查看https是否已經開啓:

netsh http show sslcert

結果未看到顯示開啓的端口,此時想或許是不是權限不夠呢?因而將啓動進程的身份運行以【管理員】身份運行,在上述啓動進程中添加以下代碼:

 p.StartInfo.Verb = "runas";

此時並未有什麼影響,須要開啓的端口依然巋然不動,將WebAPi寄宿在Windows服務中,咱們是利用批處理來進行,因而嘗試利用批處理部署一下來看看,看下演示結果:

 

利用批處理則圓滿完成任務並知足要求,不知道爲什麼直接並用管理員身份運行不可,或許是權限仍是不夠吧,至少仍是找到了一種可行的解決方案!

沒法正確返回結果

當一切準備就緒時,前端又反應返回的結果不正確不能解析,一波未平一波又起,繼續fighting,在控制檯以下顯示:

這個結果聞所未聞,竟然出現一個【 k__BackingField 】 字段,通過查詢相關資料得出:在WebAPi中默認是利用JSON.Net中的【 DefaultContractResolver 】來解析對象,當對解析對象上添加【 Serializable 】對象時則會形成上述緣由,例如:

    [Serializable]
    public class Person
    {
        public int Age { get; set; }
    }

此時應將【Serializable】特性去除或者在全局配置添加以下語句才能正確顯示須要的結果,來忽略默認特性:

            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
  new DefaultContractResolver { IgnoreSerializableAttribute = true };

【建議】:不管是有無添加上述序列化特性,建議都在全局配置添加上述忽略默認序列化特性。

參考資料:

http://stackoverflow.com/questions/29701891/k-backingfield-remove-in-c-sharp-seen-via-swashbuckle-swagger

http://stackoverflow.com/questions/35259333/jsonmediatypeformatter-formatting-with-k-backingfield

這一切是否是就這樣完了呢?任務算是完成了,爲了對證書有更加深刻的理解,咱們來拓展一下證書知識。

證書知識擴展

兩個服務器之間是如何利用證書來進行相互之間的信任呢?請看下圖:

在如上圖中,管理員經過交換雙方之間的公鑰中的指紋來創建兩者服務器之間的信任關係。對於證書上述咱們也已經演示在.NET中經過【 X509Certificate2 】來實現,下面咱們來看看這個類。

第一點:證書和PKCS #12/PFX文件的不一樣 

X509Certificate2此類有兩個屬性即Public Key(公鑰)和Private Key(私鑰),當咱們導入證書時能夠是否導出該私鑰,在Windows中典型的證書是以擴展名.cer結尾,固然它沒有包含私鑰。

下面咱們能夠這樣導出一個證書:

            var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentCA.cer";
            var cert = new X509Certificate2(fileName);
            File.WriteAllBytes(@"d:Hello.cer", cert.Export(X509ContentType.Cert));

有時咱們須要導出私鑰,此時私鑰及擴展名.Pfx結尾在另一個文件,能夠經過以下導出:

File.WriteAllBytes("Hello.pfx", cert.Export(X509ContentType.Pkcs12, (string)null));

Hello.pfx實際上就是一個PKCS#12文件,它能夠做爲一個單獨文件來儲存須要加密對象,作廣泛的用途固然也就是用X509Certificate2來存儲私鑰,有關更多知識請參考:

https://en.wikipedia.org/wiki/PKCS_12

第二點:證書存儲

關於這點上述也已經演示,咱們經過MMC打開的是控制檯證書管理器,能夠將當前用戶或本地計算機帳戶導入其中,若只是想看當前用戶證書則能夠經過certmgr.msc來打開。那麼經過代碼形式如何進行呢?以下:

var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
store.Close();

能夠將證書導入到經過StoreLocation.CurrentUser映射到當前用戶,經過StoreName.My映射到當前用戶我的證書中,也能夠是本機計算機帳戶中的其餘機構中經過上述枚舉便可。此時則會添加以下注冊表中

HKEY_CURRENT_USER\SOFTWARE\Microsoft\SystemCertificates

或者經過桌面路徑

C:\Users\username\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates

固然在本地計算中則是以下路徑:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates

第三點:理解私鑰

咱們上述已經敘述過證書中是不帶私鑰,私鑰時單獨做爲一個文件來使用,那麼私鑰到底存儲在什麼地方呢?咱們給出以下代碼:

            var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx";
            var cert = new X509Certificate2(fileName,"password", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.ReadWrite);
            store.Add(cert);
            store.Close();

此時私鑰會存儲在以下注冊表中:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\MY\Keys

若是咱們進行以下修改將MachineKeySet修改成UserKeySet:

            var fileName = AppDomain.CurrentDomain.BaseDirectory + "DevelopmentSSL.pfx";
            var cert = new X509Certificate2(fileName,"password", X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.ReadWrite);
            store.Add(cert);
            store.Close();

此時則會存儲在以下地方:

C:\Users\username\AppData\Roaming\Microsoft\SystemCertificates\My\Keys\

此時可能會出現一點問題,當將證書導入並供本地計算機去使用,可是此時私鑰卻我的用戶文件夾中,要是計算機中的其餘帳戶想要訪問這個私鑰可能沒有這個權限或者說得不到這個私鑰。

關於枚舉X509KeyStorageFlags的幾點說明:

(1)Exportable :建立證書時指定它能夠用於備份私鑰。

(2)PersistKeySet:建立證書時指定它能夠導入一次並使用屢次。

(3)UserKeySet:建立證書時指定它能夠在另一個帳戶使用它。

(4)MachineKeySet:建立證書時指定它可能致使其餘帳戶沒有權限或者訪問不到私鑰致使該私鑰不存在。

第四點:建立證書時謹慎

謹慎導出證書利用字節數組 var certificate = new X509Certificate2(bytes); 此時會將文件寫到臨時文件夾中,此時有可能臨時文件夾中有關此文件不會獲得有效的清理,致使臨時文件夾膨脹。

工具介紹

以下網址這裏能夠看到經過MakeCert程序來建立證書已經被棄用(在PowerShell4.0以前咱們能夠下載MakeCert來自建立證書)。

https://msdn.microsoft.com/library/windows/desktop/aa386968.aspx

如今建立的證書能夠經過PowerShell來進行(不過系統在Windows 8 或者Windows Server 2012或者Windows 8.1或者Windows Server 2012 R2),有關此命令的介紹詳情請見以下網址:

https://technet.microsoft.com/library/hh848633

看起來好像很難,實則比MakeCert命令更加簡潔明瞭,咱們來看看(須要切換到PowerShell)。

(1)利用以下命令來建立證書並獲取到其指紋

 New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname localhost

頒發給localhost,並將其保存到本地計算機中的【我的】證書下,結果獲得以下:

(2)須要導出證書時,須要用一個變量來保存密碼

$pwd = ConvertTo-SecureString -String "Pa$$w0rd" -Force -AsPlainText

(3)導出pfx,指定第一步獲取到的指紋和第二步保存的密碼

Export-PfxCertificate -cert cert:\localMachine\my\CE0976529B02DE058C9CB2C0E64AD79DAFB18CF4 -FilePath d:cert.pfx -Password $pwd

總結 

本節到這裏算是徹底結束了,將在WebAPi使用過程當中遇到的問題到一併敘述了一遍,其中整個作完花費了三天時間,寫這篇博客花費了一天,好久沒有坐着花費接近一天的精力來寫一篇博客,不過確確實實漲了很多知識,文中有關內容如有不妥之處,歡迎批評,同時也爲了後續讓其餘須要用到的童鞋少走點坑也是值得的,固然這裏在WebAPi中咱們也須要認證客戶端是否已經採用ssl加密證書,經過繼承DelegatingHandler來進行處理,例如以下:

            var uri = new UriBuilder(request.RequestUri);
            uri.Scheme = Uri.UriSchemeHttps;
            uri.Port = _httpsPort;
            //TO DO

在這裏很是感謝園友【幻天芒】,遇到難題解決不了或是沒什麼思路都在向他請教,感謝他的不厭其煩和指導,再次表示感謝。在這裏也提早預祝各位園友國慶快樂。

參考資料

http://stackoverflow.com/questions/29701891/k-backingfield-remove-in-c-sharp-seen-via-swashbuckle-swagger

http://stackoverflow.com/questions/779228/the-parameter-is-incorrect-error-using-netsh-http-add-sslcert?answertab=votes

http://southworks.com/blog/2014/06/16/enabling-ssl-client-certificates-in-asp-net-web-api/

http://stackoverflow.com/questions/28854466/makecert-exe-error

http://stackoverflow.com/questions/6307886/how-to-create-pfx-file-from-certificate-and-private-key/18704221#18704221

http://paulstovell.com/blog/x509certificate2

http://stackoverflow.com/questions/35259333/jsonmediatypeformatter-formatting-with-k-backingfield

http://www.thewindowsclub.com/disable-insecure-content-warning-chrome

http://windowsitpro.com/blog/creating-self-signed-certificates-powershell

相關文章
相關標籤/搜索