[原创]x.509证书在WCF中的应用(CS篇)

为什么要用x.509证书?
WCF的服务端和客户端之间,如果不作任何安全处理(即服务端的<security mode="None">),则所有传输的消息将以明文方式满天飞,在internet/intranet环境下无疑是很不安全的,这就是用证书的目的。(当然WCF还有其它安全机制,比如最常见的UserName方式,但通常每次都要从数据库读取用户名/密码信息进行验证,比较麻烦,开销也大,个人觉得还是证书最为方便)--关于x.509证书

的基本知识,可参见http://www.cnblogs.com/yjmyzz/archive/2008/08/19/1271171.html

大致原理(个人理解,可能不太准确):
正确设置服务端与客户端证书后,WCF的服务端启动时,需要利用服务端证书验证,如果验证通过将正常启动,否则报异常,同时客户端调用服务端方法时,也需要提供客户端证书,服务端接受到客户端证书后,验证客户端证书的有效性,如果通过,则允许客户端正常调用。

 

下面将逐步讲解如何使用:

1.制作证书

先进入到vs2008的命令行状态,即:
开始-->程序-->Microsoft Visual Studio 2008-->Visual Studio Tools-->Visual Studio 2008 命令提示

键入:

makecert -r -pe -n  " CN=MyServer "  -ss My -sky exchange

解释一下:makecert.exe是一个专门用来制作证书的小工具,上面一行的意思就是制作一个CN=MyServer的服务器证书,默认存储在CurrentUser\My这个位置,同时这个证书标识为可导出。(详细的

MakeCert参数可参见http://msdn.microsoft.com/zh-cn/bfsktky3(vs.80).aspx)

再输入:

makecert -r -pe -n  " CN=MyClient "  -ss My -sky exchange

生成客户端证书,证书生成好以后,可以在IE里查看到,IE-->工具-->Internet选项-->内容-->证书

同时如何管理已经安装的证书,可参见http://www.cnblogs.com/yjmyzz/archive/2008/08/20/1272128.html

2.wcf服务端

vs.net2008启动后,新建一个控制台应用程序-->(右击)添加-->新建项-->WCF服务-->命名为MyService.cs-->保存

保存后,系统会自动生成一个接口文件IMyService.cs

二个文件的内容如下:
IMyService.cs

using  System;  
using  System.ServiceModel;

namespace  Server
{
    
//  注意: 如果更改此处的接口名称 "IMyService",也必须更新 App.config 中对 "IMyService" 的引用。
    [ServiceContract]
    
public   interface  IMyService
    {
        [OperationContract]
        
string  Test();

    }
}


MyService.cs

using  System; 
using  System.ServiceModel;
namespace  Server
{
    
//  注意: 如果更改此处的类名 "MyService",也必须更新 App.config 中对 "MyService" 的引用。
     public   class  MyService : IMyService
    {
        
public   string  Test()
        {
            Console.WriteLine(
" 服务端输出:\n "   +  ServiceSecurityContext.Current.PrimaryIdentity.AuthenticationType);
            Console.WriteLine(ServiceSecurityContext.Current.PrimaryIdentity.Name);
            
return   " 服务端时间: "   +  DateTime.Now.ToString(); 
        }
    }
}

再来新建一个cs文件:CustomX509CertificateValidator.cs
内容先贴在下面

using  System;
using  System.Security.Cryptography.X509Certificates;
using  System.IdentityModel.Tokens;
using  System.IdentityModel.Selectors;

namespace  Server
{
    
public   class  CustomX509CertificateValidator : X509CertificateValidator
    {
        
public   override   void  Validate(X509Certificate2 certificate)
        {
            Console.WriteLine(certificate.Subject);
            Console.WriteLine(certificate.Thumbprint); 
            
if  (certificate.Thumbprint  !=   "3E4D4B64A90810B6CFF9B1DD2390D8C9488747BF " )
                
throw   new  SecurityTokenException( " Certificate Validation Error! " );
        }
    }
}

 

注意:项目必须先添加对System.IdentityModel的引用

解释一下:
这个文件的用户是:客户端要调用服务端方法,并提供客户端证书时,用来验证客户端证书的有效性。注意里面的if (certificate.Thumbprint != "3E4D4B64A90810B6CFF9B1DD2390D8C9488747BF")这一句,大家调试的时候,里面的3E4D4B64A90810B6CFF9B1DD2390D8C9488747BF要换成你自己的客户端证书的信息(每一个证书对应的这一串字符都是唯一的),可通过在IE浏览器里,查看MyClient证书的详细信息得到,见下图:


同时注意配置文件App.Config,内容如下

<? xml version = " 1.0 "  encoding = " utf-8 "   ?>
< configuration >
    
< system.serviceModel >
        
< behaviors >
            
< serviceBehaviors >
                
< behavior name = " Server.MyServiceBehavior " >
                  
< serviceMetadata httpGetEnabled = " true "  httpGetUrl = " http://localhost:8080 "   />
                  
< serviceDebug includeExceptionDetailInFaults = " true "   />
                  
< serviceCredentials >
                    
< clientCertificate >
                      
< authentication certificateValidationMode = " Custom "  customCertificateValidatorType = " Server.CustomX509CertificateValidator,Server " />
                    
</ clientCertificate >
                    
< serviceCertificate findValue = " MyServer "  storeLocation = " CurrentUser "
                      x509FindType
= " FindBySubjectName "   />
                  
</ serviceCredentials >
                
</ behavior >
            
</ serviceBehaviors >
        
</ behaviors >
      
< bindings >
        
< netTcpBinding >
          
< binding name = " NewBinding0 " >
            
< security mode = " Transport " >
              
< transport clientCredentialType = " Certificate "   />
            
</ security >
          
</ binding >
        
</ netTcpBinding >
      
</ bindings >
        
< services >
            
< service behaviorConfiguration = " Server.MyServiceBehavior "  name = " Server.MyService " >
                
< endpoint address = " net.tcp://localhost:8081 "  binding = " netTcpBinding "  contract = " Server.IMyService "  bindingConfiguration = " NewBinding0 " />               
            
</ service >
        
</ services >
    
</ system.serviceModel >
</ configuration >

 

解释一下:
<authentication certificateValidationMode="Custom" customCertificateValidatorType="Server.CustomX509CertificateValidator,Server"/></clientCertificate>
这一行的意思就是通知WCF服务端,验证客户端证书的模式为自定义,验证时调用Server.CustomX509CertificateValidator这个类来完成验证

<serviceCertificate findValue="MyServer" storeLocation="CurrentUser" x509FindType="FindBySubjectName" />
这一行的意思是WCF服务端验证证书时,到CurrentUser这个位置查询CN=MyServer的证书

最后在Program.cs里启用WCF,内容如下:

using  System;  
using  System.ServiceModel;

namespace  Server
{
    
class  Program
    {
        
static   void  Main( string [] args)
        {
            ServiceHost host 
=   new  ServiceHost( typeof (MyService));
            host.Open();
            Console.ReadKey();
        }
    }
}

 

build一下,如果编译无错的话,服务端完工,可以运行一下,将弹出一个DOS命令窗口(不过什么输出也没有),只要不报错,就表明Ok了,先不要急着关,尝试浏览一下:

http://localhost:8080/(这个地址哪里来的?回头看下App.config,里面有一行<serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:8080/" />,呵呵,明白了吧) 正常的话,应该类似下图所示:

3.客户端调用

下面生成服务端的代理和配置文件,客户端开发将用到这二个文件,同样先进入vs2008的命令行状态,输入:

svcutil.exe http://localhost:8080/ /d:c:\123\

注意:输入这一行命令的时候,请确保服务端程序正在运行。这一句的意思就是在c:\123\目录下输出WCF的代理文件和配置文件

打开vs.net2008,再新建一个控制台应用程序,可以命名为Client

把c:\123\下生成的二个文件MyService.cs,output.config添加到Client项目中,同时将output.config改名为App.Config

Progam.cs代码内容如下:

using  System; 
namespace  Client
{
    
class  Program
    {
        
static   void  Main( string [] args)
        {
            
using  (MyServiceClient client  =   new  MyServiceClient())
            {
                Console.WriteLine(
" 客户端输出: " );
                Console.WriteLine(client.Test());
            } 
            Console.ReadKey();
        }
    }
}

同时,参考下面的内容手动修改一下App.Config文件

<? xml version="1.0" encoding="utf-8" ?>
< configuration >
    
< system.serviceModel >
      
< behaviors >
        
< endpointBehaviors >
          
< behavior  name ="NewBehavior" >
            
< clientCredentials >
              
< clientCertificate  findValue ="MyClient"  x509FindType ="FindBySubjectName" />
              
< serviceCertificate >
                
< authentication  certificateValidationMode ="None"   />
              
</ serviceCertificate >
            
</ clientCredentials >
          
</ behavior >
        
</ endpointBehaviors >
      
</ behaviors >
      
< bindings >
            
< netTcpBinding >
                
< binding  name ="NetTcpBinding_IMyService"  closeTimeout ="00:01:00"
                    openTimeout
="00:01:00"  receiveTimeout ="00:10:00"  sendTimeout ="00:01:00"
                    transactionFlow
="false"  transferMode ="Buffered"  transactionProtocol ="OleTransactions"
                    hostNameComparisonMode
="StrongWildcard"  listenBacklog ="10"
                    maxBufferPoolSize
="524288"  maxBufferSize ="65536"  maxConnections ="10"
                    maxReceivedMessageSize
="65536" >
                    
< readerQuotas  maxDepth ="32"  maxStringContentLength ="8192"  maxArrayLength ="16384"
                        maxBytesPerRead
="4096"  maxNameTableCharCount ="16384"   />
                    
< reliableSession  ordered ="true"  inactivityTimeout ="00:10:00"
                        enabled
="false"   />
                    
< security  mode ="Transport" >
                        
< transport  clientCredentialType ="Certificate"  protectionLevel ="EncryptAndSign"   />
                        
< message  clientCredentialType ="Windows"   />
                    
</ security >
                
</ binding >
            
</ netTcpBinding >
        
</ bindings >
        
< client >
            
< endpoint  address ="net.tcp://localhost:8081/"  binding ="netTcpBinding"
                bindingConfiguration
="NetTcpBinding_IMyService"  contract ="IMyService"
                name
="NetTcpBinding_IMyService"  behaviorConfiguration ="NewBehavior" >
                
< identity >
                    
< dns  value ="MyServer"   />
                
</ identity >
            
</ endpoint >
        
</ client >
    
</ system.serviceModel >
</ configuration >

 

主要是增加了一个节点
<behaviors>
        <endpointBehaviors>
          <behavior name="NewBehavior">
            <clientCredentials>
              <clientCertificate findValue="MyClient" x509FindType="FindBySubjectName"/>
              <serviceCertificate>
                <authentication certificateValidationMode="None" />
              </serviceCertificate>
            </clientCredentials>
          </behavior>
        </endpointBehaviors>
      </behaviors>

上面红色的行,就是表明客户端调用时,将用MyClient证书来验证

 

同时<endpoint address="net.tcp://localhost:8081/" binding="netTcpBinding"
                bindingConfiguration="NetTcpBinding_IMyService" contract="IMyService"
                name="NetTcpBinding_IMyService" behaviorConfiguration="NewBehavior">这一句,增加了behaviorConfiguration="NewBehavior"

好了,Build一下,没有问题的话,开发完成


4.测试:
先启动服务端,再启动客户端,运行结果如下:

(转贴请注明来自"菩提树下的杨过") http://www.cnblogs.com/yjmyzz/archive/2008/08/20/1272550.html

 

注意服务端server.exe输出的信息中3E4D4B64A90810B6CFF9B1DD2390D8C9488747BF与客户端证书完全吻合

最后来谈谈分发问题,上面这一系列测试都是在同一台机器完成的,客户端总不可能总是跟服务端在一台机器上,这个好办,在IE里把MyClient证书导出,注意导出时要选择"是,导出私钥",然后把导出的pfx文件连同客户端程序一起分发到目标客户机即可,这里要注意几点:

a.客户端上的App.config里,要把<endpoint address="net.tcp://localhost:8081/" 中的localhost换成服务端的Ip地址
b.注意防火墙参数设置(本例中,即要把tcp:8081端口打开)

你可能感兴趣的:(WCF)