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下载

  

  证书demo

  

    证书+帐号密码demo

--------------------------------------------------------------------------------更新01------------------------------------------------------------------------------------------------------

  如果web访问配置好的服务提示“密钥集不存在”的问题,请按一下方法处理:

  进入路径:C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys(vista之后可用)

  找到刚才创建的证书文件,如果你不确定,可以参考这里

  然后右键-属性-安全,保证IIS_IUSRS用户有读取该文件的权限(本机测试时IIS是由这个用户运行的,其他电脑可能会有不同。)即可。

最新文章

  1. 创业日志N,一听到别人说创业我就怕
  2. WebView 的使用----android 网络连接处理分析
  3. Java反射机制&lt;2&gt;
  4. Java 并发-任务执行.
  5. 转:The Knuth-Morris-Pratt Algorithm in my own words
  6. 急缺【jQuery】人才,要求熟悉jQuery,熟悉Js,熟悉前端开发
  7. python面试题大全
  8. 模板方法模式(TemplateMethod)
  9. Amazon SQS简单介绍 上篇
  10. HDU2066一个人的旅行/最短路问题
  11. Quick Cocos2dx Http通讯 JSON
  12. selenium 之 ActionChains (二)
  13. JDK源码阅读(1)_简介+ java.io
  14. (七):C++分布式实时应用框架 2.0
  15. C++关于string的一些用法
  16. Python之文件和目录操作
  17. rs232接口定义
  18. Jenkins+Git+Maven构建并部署war包到tomcat
  19. PostgreSQL分页
  20. 【python练习题】程序8

热门文章

  1. 跳一跳外挂的python实现--OpenCV步步精深
  2. I Love Palindrome String HDU - 6599 回文树+hash
  3. 2019-4-29-WPF-如何判断一个控件在滚动条的里面是用户可见
  4. elasticsearch.net一个查询问题
  5. 40. 组合总和 II
  6. lxhgww的奇思妙想 长链剖分板子
  7. QT中QString与string的转化,解决中文乱码问题
  8. Http学习(三)
  9. EJB(Enterprise JavaBean)科普
  10. day23_2_logging