前言

话说又来需求了,之前对于在SelfHost中需要嵌套页面并操作为非正常需求,这回来正常需求了,客户端现在加了https,老大过来说WebAPi访问不了了,这是什么情况,我去试了试,还真是这个情况,不知道如何下手啊,最终为了解决这个问题,漫长的探索之旅就这样开始了,希望给需要在SelfHost下启动Https的童鞋一点启示和帮助。

话题引入

当在客户端发送请求发送不过去时看了看谷歌控制台显示的消息大意是不能混合使用即客户端为https,则WebAPi不能为http,估计是为了安全的问题,于是乎问题就比较明朗了,只需要将WebAPi修改成https即可,直接将SelfHost中地址修改为Https显然是不行的必须要借助证书并开启对应的端口才行,既然我们已经分析完毕,接下来我们解决WebAPi启动Https的问题即可。开始想到去申请的一个免费的证书就行,但是由于是将WebAPi安装在本地Windows服务中且ip不固定,所以免费的证书则不再可取,只能自行创建证书来解决这个问题。但是我对于证书几乎从未接触过,一无所知,不知从哪里下手,这一漫长的过程就从此开始。

解决SelfHost启动Https

在园中搜索有关自创建证书的答案,基本要么是IIS,要么是WCF创建证书,不过还是有了一点思路,提供了自创建证书需要的命令,开始有一点思绪。当看到如下一条命令时我要惊呆了,难道就这样解决了吗,so  easy!

第一次尝试

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

结果发现这只是url的保留项而已。

第二次尝试

我们通过VS开发命令来创建证书

第一步:

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)】

结果变成如下:

第二步:

打开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

https WebAPi的更多相关文章

  1. 高德地图JavaScript开发

    项目需求:标注一个或者两个点.显示信息窗体.自定义icon <!DOCTYPE html> <html lang="en"> <head> &l ...

  2. avalon学习笔记一 列表及条件过滤

    好长时间都没有更新博客了,不是因为没有学习新的东西,而是到了新的单位每天玩命加班实在是太累了!经过一年的努力吧,终于可以轻松一下了.废话少说,直接干货吧! 由于是学习阶段,就直接拿了公司的二级页面做了 ...

  3. 微信小程序初体验--封装http请求

    最近看了一下微信小程序,大致翻了一下,发现跟angular很相似的,但是比angular简单的很多具体可参考官方文档 https://mp.weixin.qq.com/debug/wxadoc/dev ...

  4. vue 调用高德地图

    一. vue-amap,一个基于 Vue 2.x 和高德地图的地图组件 https://elemefe.github.io/vue-amap/#/ 这个就不细说了,按照其文档,就能够安装下来. 二. ...

  5. vue 高德地图之玩转周边

    前言:在之前的博客中,有成功引入高德地图,这是以前的地址  vue 调用高德地图. 因为一些需求,需要使用到地图的周边功能. 完整的项目代码请查看  我的github 一 .先看要实现的结果,参考了链 ...

  6. 高德JSAPI获取当前所在位置的经度纬度

    这是在浏览器中的效果: 控制台打印出来的就是经度纬度的值 代码如下: <!doctype html> <html> <head> <meta charset= ...

  7. gps 经纬度 转换实际距离

    <!doctype html> <html lang="zh-CN"> <head> <meta charset="utf-8& ...

  8. WEB前端学习代码片段记录

    1.JS设计模式片段 Function.prototype.addMethod = function (name,fn) { this.prototype[name] = fn; return thi ...

  9. js通过高德地图获取当前位置的经度纬度

    效果图如下: 已经获取到了经度纬度了 代码如下: <!doctype html> <html> <head> <meta charset="utf- ...

随机推荐

  1. UIAlertController:弹框4步走

    对于大多数的App来说,弹框这种交互方式基本上是必不可少的.在新的iOS系统中(具体版本我忘了),以前的UIAlertView被弃用,而换成了现在的UIAlertController. UIAlite ...

  2. SSKeyChains的使用小节

    我是前言: 最近在项目中需要使用钥匙串来进行账户密码的保存,小结一下.贴上框架地址:https://github.com/soffes/SAMKeychain. 它提供了5个类方法使用: + (NSA ...

  3. canvas绘制简单小铅笔

    对应HTML <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <ti ...

  4. SAP HANA STRING_AGG

    HANA Version 1.00.73.00.389160 不支持STRING_AGG,所以只能,,,,,,,, DROP PROCEDURE ""."ZCONCAT_ ...

  5. request对象方法详解

    自己整理的 javax.servlet.http.HttpServletrequest 所有方法,欢迎收藏! 方法名 说明 isUserInRole 判断认证后的用户是否属于某一成员组 getAttr ...

  6. leetCode 53.Maximum Subarray (子数组的最大和) 解题思路方法

    Maximum Subarray  Find the contiguous subarray within an array (containing at least one number) whic ...

  7. Android Socket编程基础

    前些天写了一个Android手机在局域网内利用Wifi进行文件传输的Demo,其中用到了Socket编程,故此总结(盗了网友的一些图和文字).好久好久没来博客园了~~ 1.什么是Socket? soc ...

  8. oracle本月、上月、去年同月第一天最后一天

    select trunc(sysdate, 'month') 本月第一天,        trunc(last_day(sysdate)) 本月最后一天,        trunc(add_month ...

  9. [ES7] Exploring ES2016 Decorators

    Original artial --> link How descorator looks like: @mydecorator function myFun(){ ... } Descorat ...

  10. ios下微信标题修改

    很多开发过微信的人估计都遇到过这样的问题,ios下微信页面标题更改不了,而安卓却可以直接用:document.title="你的标题". 下面是解决这个问题的hack: 1.jqu ...