话说又来需求了,以前对于在SelfHost中须要嵌套页面并操做为非正常需求,这回来正常需求了,客户端如今加了https,老大过来讲WebAPi访问不了了,这是什么状况,我去试了试,还真是这个状况,不知道如何下手啊,最终为了解决这个问题,漫长的探索之旅就这样开始了,但愿给须要在SelfHost下启动Https的童鞋一点启示和帮助。前端
当在客户端发送请求发送不过去时看了看谷歌控制台显示的消息大意是不能混合使用即客户端为https,则WebAPi不能为http,估计是为了安全的问题,因而乎问题就比较明朗了,只须要将WebAPi修改为https便可,直接将SelfHost中地址修改成Https显然是不行的必需要借助证书并开启对应的端口才行,既然咱们已经分析完毕,接下来咱们解决WebAPi启动Https的问题便可。开始想到去申请的一个免费的证书就行,可是因为是将WebAPi安装在本地Windows服务中且ip不固定,因此免费的证书则再也不可取,只能自行建立证书来解决这个问题。可是我对于证书几乎从未接触过,一无所知,不知从哪里下手,这一漫长的过程就今后开始。web
在园中搜索有关自建立证书的答案,基本要么是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 machine
-personal
. 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/35259333/jsonmediatypeformatter-formatting-with-k-backingfield
这一切是否是就这样完了呢?任务算是完成了,为了对证书有更加深刻的理解,咱们来拓展一下证书知识。
两个服务器之间是如何利用证书来进行相互之间的信任呢?请看下图:
在如上图中,管理员经过交换双方之间的公钥中的指纹来创建两者服务器之间的信任关系。对于证书上述咱们也已经演示在.NET中经过【 X509Certificate2 】来实现,下面咱们来看看这个类。
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://southworks.com/blog/2014/06/16/enabling-ssl-client-certificates-in-asp-net-web-api/
http://stackoverflow.com/questions/28854466/makecert-exe-error
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