WCF作为.net三大组件之一,伟大之处不用多说,但是其加密配置对于我这样的萌新来说还是颇有难度,因此将几天来的研究成果共享出来,与各位共勉~
首先声明我的开发环境,Win10创意者更新 + Visual Studio 2015 update3 + .Net 4.5 + iis10
一、创建X.509证书
1、创建证书
可通过PowerShell或者makecert工具两种方式,个人建议使用参考资料更多后者,但最新的Windows和VS都不带makecert,所以需要的话可以到文章结尾处下载。
使用CMD运行:
makecert -sr CurrentUser -ss My -n CN=HelloServiceClient -sky exchange -pe -r
提示Succeded即创建完成。
此时将在当前用户下的个人项目中看到这个证书,图中MMC管理单元的使用可以参考这里。
2、设置为信任
由于创建的证书在个人域,且不在信任链中,wcf和iis目前不能使用这个证书,一次需要将其设置为信任。
首先先将其导出到磁盘:证书上右键--所有任务--导出--选择导出私钥--设置私钥密码,完成后将得到一个pfx文件。
然后进入上图的本地计算机,在个人域导入刚才那个pfx文件,完成后双击证书,在“证书路径”标签中提示“由于CA 根证书不在“受信任的根证书颁发机构”存储区中,所以它不受信任。”,此时证书仍然不能被使用,我的做法是在本地计算机的“受信任的根证书颁发机构”重复导入一次。此时两个证书都变成可信,即使将第二次导入的删除也没关系。
以上做完没问题的话,双击证书后的状态应该是这样的:
二、通过证书加密的项目
1、创建wcf服务
VS中新建“WCF服务应用程序”的项目,命名为WCF_HelloService,此时不用任何修改,已经是可运行的wcf服务,然后将其部署到iis,在浏览器中可使用http访问到服务信息:
并重写Service1.scv.cs中的GetData()方法:
public string GetData(int value) { if (ServiceSecurityContext.Current != null) { if (!ServiceSecurityContext.Current.IsAnonymous) { return "Hello:" + ServiceSecurityContext.Current.PrimaryIdentity.Name + ";type=" + ServiceSecurityContext.Current.PrimaryIdentity.AuthenticationType; } return "Hello,你输入的是:" + value; } return "Hello ||未检测到证书:" + value; }
下面是重点,编辑服务的Web.config文件,使其访问证书,这里尤其注意要注意用于各项配置互调的名称设置,如behaviorConfiguration和bindingConfiguration等:
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/> </appSettings> <system.web> <compilation debug="true" targetFramework="4.5.2"/> <httpRuntime targetFramework="4.5.2"/> <httpModules> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"/> </httpModules> </system.web> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <remove name="ApplicationInsightsWebTracking"/> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler"/> </modules> <!-- 若要在调试过程中浏览 Web 应用程序根目录,请将下面的值设置为 True。 在部署之前将该值设置为 False 可避免泄露 Web 应用程序文件夹信息。 --> <directoryBrowse enabled="true"/> <validation validateIntegratedModeConfiguration="false"/> </system.webServer> <system.serviceModel> <services> <service name="WCF_HelloService.HelloService" behaviorConfiguration="CustomBehavior"> <endpoint binding="mexHttpBinding" contract="IMetadataExchange" address="mex" /> <endpoint address="" binding="wsHttpBinding" contract="WCF_HelloService.IHelloService" bindingConfiguration="CustomBinding"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="CustomBehavior"> <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 --> <serviceMetadata httpGetEnabled="true"/> <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 --> <serviceDebug includeExceptionDetailInFaults="false"/> <!--add by Lbh--> <serviceCredentials> <!-- 服务端采用证书详细配置 findValue :创建证书名称 storeName:证书储存详细位于哪 storeLocation :证书储存位于当前本机用户 X509FindType : x509查找证书主题名--> <serviceCertificate findValue="HelloServiceClient" storeName="My" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/> <!--客户端验证方式--> <clientCertificate> <authentication certificateValidationMode="None"/> </clientCertificate> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> <!--add by Lbh--> <bindings> <wsHttpBinding> <binding name="CustomBinding"> <!--验证方式--> <security mode="Message"> <message clientCredentialType="Certificate"/> </security> </binding> </wsHttpBinding> </bindings> </system.serviceModel> </configuration>
添加add by 注释是添加的内容,注意serviceCertificate节点,这里定义了目的证书的信息,请务必使其指向我们刚才配置好的证书,其他诸如命名空间、接口、类名等也应与项目对应。
配置完成后如无问题,刷新刚才的web页面,我们仍然能看到服务启动成功的页面。
2、配置客户端
随便添加个winform程序,首先引用上面的服务,然后修改其app.config,同样需要注意behaviorConfiguration设置:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <system.serviceModel> <bindings> <wsHttpBinding> <binding name="WSHttpBinding_IHelloService"> <security mode="Message"> <transport clientCredentialType="Windows" /> <message clientCredentialType="Certificate" /> </security> </binding> </wsHttpBinding> </bindings> <!--add by Lau--> <behaviors> <endpointBehaviors> <behavior name="CustomBehavior"> <clientCredentials> <clientCertificate findValue="HelloServiceClient" storeName="My" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/> <serviceCertificate> <authentication certificateValidationMode="None"/> </serviceCertificate> </clientCredentials> </behavior> </endpointBehaviors> </behaviors> <client> <endpoint address="http://localhost:8096/HelloService.svc" behaviorConfiguration="CustomBehavior" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IHelloService" contract="HelloService.IHelloService" name="WSHttpBinding_IHelloService"> <identity> <certificate encodedValue="AwAAAAEAAAAUAAAAmIXXyLpHnm+H6oDaCP03aIn03SsgAAAAAQAAABUCAAAwggIRMIIBeqADAgECAhC1V8uCAl/avEkX078G+PlRMA0GCSqGSIb3DQEBBAUAMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudDAeFw0xNzA1MDgwNzE1NDBaFw0zOTEyMzEyMzU5NTlaMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1+nEnhCxXtfAFxOGgFgzBjcPeO2WmxQI5SC14e6S4yEz+ymJtfKBcEnRSCX7onQDRE5H9dPl9CqoNjI/nkU5OKZ789f5Jh7ISfDK0jfHPa2EYwKK3FwOwGFmx5YY2/7Eb/nmyq6gbroronBIioFU6mcZjkFmTQTDa2WnZJMIsikCAwEAAaNSMFAwTgYDVR0BBEcwRYAQhYkF0TiSQwHAV/0wgMmvE6EfMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudIIQtVfLggJf2rxJF9O/Bvj5UTANBgkqhkiG9w0BAQQFAAOBgQA0LvNliWDaWtU4YkqXI8JU9/2mIHO2PK4EVUmUYJu0oxFNEeRcX8ZpAAAA26gRYN+J4IjC1F33NjRG/tzkGJeaTBdOl2SkJo8LqD2D7YfOcMaXfrAsAOcEP5e4z2Z4aZlZp1tOjf0X5SZ6QL4FbPiiJog+1UbF/z5J097peDU7Bw==" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>
与服务端类似地,clientCertificate节点定义了客户端证书,本例中使用了服务端相同的证书,也可以创建另一个专供客户端使用。certificate节点的内容来自服务端,引用WCF服务操作完成后会自动生成,如果没有,请检查WCF的web.config中是否定义为baseHttpBinding而不是wsHttpBinding(正确的是后者)。
最后在winform加上基本的button和txtResult,并在button按钮事件写入代码:
private void button1_Click(object sender, EventArgs e) { try { HelloService.HelloServiceClient client = new HelloService.HelloServiceClient(); string result = client.GetData(DateTime.Now.Second); txtResult.Text = result; } catch (Exception ex) { this.txtResult.Text = ex.ToString(); } }
运行程序,得到正常结果如图:
并且通过http拦截到的都是密文:
至此,第一个证书项目完成,demo请到文章结尾处下载。
三、通过证书+帐号密码加密的项目
1、创建WCF服务
按上面步骤创建好服务,首先添加IdentityModel库的引用:
然后创建用于校验的CustomUserPassword类,代码如下:
using System.IdentityModel.Selectors; using System.ServiceModel; namespace TestUserPassService { public class CustomUserPassword : UserNamePasswordValidator { public override void Validate(string userName, string password) { if (userName != "admin" || password != "admin") { //throw new SecurityNegotiationException("验证用户名和密码时,未通过检测");// 此异常可能无法被客户端捕获 throw new FaultException("用户名或者密码错误!"); } } } }
最后修改web.config文件,可以看到增加了userNameAuthentication节点,定义的正是自定义的校验类:
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/> </appSettings> <system.web> <compilation debug="true" targetFramework="4.5.2"/> <httpRuntime targetFramework="4.5.2"/> <httpModules> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"/> </httpModules> </system.web> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <remove name="ApplicationInsightsWebTracking"/> <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler"/> </modules> <!-- 若要在调试过程中浏览 Web 应用程序根目录,请将下面的值设置为 True。 在部署之前将该值设置为 False 可避免泄露 Web 应用程序文件夹信息。 --> <directoryBrowse enabled="true"/> <validation validateIntegratedModeConfiguration="false"/> </system.webServer> <system.serviceModel> <services> <service name="TestUserPassService.Service1" behaviorConfiguration="CustomBehavior"> <endpoint binding="mexHttpBinding" contract="IMetadataExchange" address="mex" /> <endpoint address="" binding="wsHttpBinding" contract="TestUserPassService.IService1" bindingConfiguration="CustomBinding"/> </service> </services> <!--add by Lbh--> <behaviors> <serviceBehaviors> <behavior name="CustomBehavior"> <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false --> <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/> <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 --> <serviceDebug includeExceptionDetailInFaults="false"/> <serviceCredentials> <!-- 服务端采用证书详细配置 findValue :创建证书名称 storeName:证书储存详细位于哪 storeLocation :证书储存位于当前本机用户 X509FindType : x509查找证书主题名--> <serviceCertificate findValue="HelloServiceClient" storeName="My" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/> <!--客户端验证方式--> <clientCertificate> <authentication certificateValidationMode="None"/> </clientCertificate> <userNameAuthentication customUserNamePasswordValidatorType="TestUserPassService.CustomUserPassword,TestUserPassService" userNamePasswordValidationMode="Custom"/> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> <protocolMapping> <add binding="basicHttpsBinding" scheme="https"/> </protocolMapping> <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/> <!--add by Lbh--> <bindings> <wsHttpBinding> <binding name="CustomBinding"> <security mode="Message"> <transport clientCredentialType="Windows"/> <message clientCredentialType="UserName"/> </security> </binding> </wsHttpBinding> </bindings> </system.serviceModel> </configuration>
注意clientCredentialType节点,这里采用映射到Windows账户的方式,这是颇为常用和可靠的方式。
部署到iis,没问题的话,我们仍然可以使用浏览器通过http访问到服务。
2、创建测试客户端
新建winform客户端,首先添加引用,修改后的app.config如下:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <system.serviceModel> <bindings> <wsHttpBinding> <binding name="WSHttpBinding_IService1"> <!--add by Lbh--> <security mode="Message"> <transport clientCredentialType="Windows" /> <message clientCredentialType="UserName" /> </security> </binding> </wsHttpBinding> </bindings> <client> <endpoint address="http://localhost:8095/Service1.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1" contract="Service1.IService1" name="WSHttpBinding_IService1"> <identity> <certificate encodedValue="AwAAAAEAAAAUAAAAmIXXyLpHnm+H6oDaCP03aIn03SsgAAAAAQAAABUCAAAwggIRMIIBeqADAgECAhC1V8uCAl/avEkX078G+PlRMA0GCSqGSIb3DQEBBAUAMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudDAeFw0xNzA1MDgwNzE1NDBaFw0zOTEyMzEyMzU5NTlaMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1+nEnhCxXtfAFxOGgFgzBjcPeO2WmxQI5SC14e6S4yEz+ymJtfKBcEnRSCX7onQDRE5H9dPl9CqoNjI/nkU5OKZ789f5Jh7ISfDK0jfHPa2EYwKK3FwOwGFmx5YY2/7Eb/nmyq6gbroronBIioFU6mcZjkFmTQTDa2WnZJMIsikCAwEAAaNSMFAwTgYDVR0BBEcwRYAQhYkF0TiSQwHAV/0wgMmvE6EfMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudIIQtVfLggJf2rxJF9O/Bvj5UTANBgkqhkiG9w0BAQQFAAOBgQA0LvNliWDaWtU4YkqXI8JU9/2mIHO2PK4EVUmUYJu0oxFNEeRcX8ZpAAAA26gRYN+J4IjC1F33NjRG/tzkGJeaTBdOl2SkJo8LqD2D7YfOcMaXfrAsAOcEP5e4z2Z4aZlZp1tOjf0X5SZ6QL4FbPiiJog+1UbF/z5J097peDU7Bw==" /> </identity> </endpoint> </client> </system.serviceModel> </configuration>
可以看到,配置相比上一个项目简单许多,因为这里的客户端无需调用证书,只需定义加密类型。
添加两个textbox一个button和一个textResult,定义按钮事件代码:
using System; using System.Windows.Forms; namespace TestUserPassService_Client { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void textBox1_TextChanged(object sender, EventArgs e) { } private void textBox2_TextChanged(object sender, EventArgs e) { } private void button1_Click(object sender, EventArgs e) { try { Service1.Service1Client client = new Service1.Service1Client(); // 传入帐号密码 client.ClientCredentials.UserName.UserName = this.textBox1.Text; client.ClientCredentials.UserName.Password = this.textBox2.Text; string result = client.GetData(DateTime.Now.Second); txtResult.Text = result; } catch (Exception ex) { this.txtResult.Text = ex.ToString(); } } } }
运行客户端,正确的结果如图:
假若修改传入的帐号密码,结果如下:
查看http传输内容,同样是密文:
至此,本项目完成,demo可在文章结尾处下载。
四、总结
其实wcf加密操作没有太高深的内容(或者说暂且不用理会里面高深的内容),繁琐的部分在于web.config和app.config的配置,尤其bindingConfiguration这类名称命名上,由于网上教程众多,东拉一块西扯一块拼起来是用不了的。比如我这样的萌新调通两个项目就花了2天时间,因此这篇文章也尽可能将容易踩到的雷点暴露出来,供后来者们借鉴。当然篇幅和能力有限不能面面俱到,也请各位谅解,有问题可以在下面回复或者请教谷歌。
五、demo下载
--------------------------------------------------------------------------------更新01------------------------------------------------------------------------------------------------------
如果web访问配置好的服务提示“密钥集不存在”的问题,请按一下方法处理:
进入路径:C:ProgramDataMicrosoftCryptoRSAMachineKeys(vista之后可用)
找到刚才创建的证书文件,如果你不确定,可以参考这里
然后右键-属性-安全,保证IIS_IUSRS用户有读取该文件的权限(本机测试时IIS是由这个用户运行的,其他电脑可能会有不同。)即可。