[转贴][WCF Security] 3. X509 身份验证

原文:http://www.rainsts.net/article.asp?id=474

 

个人认为在 Intranet / Internet 环境下,最方便的认证方式应该是 X.509 数字证书。当然,还有一个原因是我用 Windows Authentication 从来没成功过。

以下我们详细描述如何创建 "Certificate Authentication"。

1. 创建数字证书

一般情况下,我们为服务器以及每个客户端都单独创建一个服务器,以便标识其唯一身份。创建数字证书时,必须添加 "-pe" 和 "-sky exchange" 参数。有关数字证书更多的信息,请参考《X.509 & RSA》。

D:\>makecert -r -pe -n "CN=MyServer" -ss My -sky exchange
D:\>makecert -r -pe -n "CN=Client1" -ss My -sky exchange



2. 创建服务

[ServiceContract]
public interface IService
{
  [OperationContract]
  string Test();
}

public class MyService : IService
{
  public string Test()
  {
    Console.WriteLine(ServiceSecurityContext.Current.PrimaryIdentity.AuthenticationType);
    Console.WriteLine(ServiceSecurityContext.Current.PrimaryIdentity.Name);

    return "Server:" + DateTime.Now.ToString();
  }
}

public class WcfTest
{
  public static void Test()
  {
    ServiceHost host = new ServiceHost(typeof(MyService));
    host.Open();
  }
}



安全方式:Transport
客户端验证类型:Certificate

在 serviceCredentials 中设置好证书的查找参数,同时将验证模式(certificateValidationMode) 设为 None (因为我们创建的是 "不受信任" 的证书)。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="NewBehavior">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:8080" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceCredentials>
            <clientCertificate>
              <authentication certificateValidationMode="None" />
            </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="NewBehavior" name="Learn.Library.WCF.MyService">
        <endpoint address="net.tcp://localhost:8081" binding="netTcpBinding"
          bindingConfiguration="NewBinding0" contract="Learn.Library.WCF.IService">
        </endpoint>
      </service>
    </services>
  </system.serviceModel>
</configuration>


3. 创建客户端

生成代理客户端。

using (ServiceClient client = new ServiceClient())
{
  Console.WriteLine(client.Test());
}


注意在配置文件中,我们在 EndPointBehaviors 中添加 ClientCredentials 来设置数字整数的相关信息。基于和上面同样的理由,我们也得将 certificateValidationMode 设为 "None"。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="NewBehavior">
          <clientCredentials>
            <clientCertificate findValue="Client1" x509FindType="FindBySubjectName" />
            <serviceCertificate>
              <authentication certificateValidationMode="None" />
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <bindings>
      <netTcpBinding>
        <binding name="NetTcpBinding_IService">
          <security mode="Transport">
            <transport clientCredentialType="Certificate" protectionLevel="EncryptAndSign" />
            <message clientCredentialType="Windows" />
          </security>
        </binding>
      </netTcpBinding>
    </bindings>
    <client>
      <endpoint address="net.tcp://localhost:8081/" behaviorConfiguration="NewBehavior"
        binding="netTcpBinding" bindingConfiguration="NetTcpBinding_IService"
        contract="ConsoleApplication1.localhost.IService" name="NetTcpBinding_IService">
        <identity>
          <dns value="MyServer" />
        </identity>
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>


运行后,我们对比一下服务器端输出结果和Client1数字证书的信息。

uploads/200704/29_103351_wcfx509_1.gif


 

uploads/200704/29_103355_wcfx509_2.gif



每个客户端的数字证书名称和序号的组合都是唯一的,我们通过它做出相应的验证动作。

4. 验证器

WCF 提供了多种数字证书的验证手段,不过我们最习惯的应该还是 "Custom",因为有很多附加行为要处理。

在服务器端添加一个继承自 X509CertificateValidator 的验证器。

public class CustomX509CertificateValidator : X509CertificateValidator
{
  public override void Validate(X509Certificate2 certificate)
  {
    //Console.WriteLine(certificate.Subject);
    //Console.WriteLine(certificate.Thumbprint);

    if (certificate.Thumbprint != "40399ADCC90BB3C4D23D2B639D4356AABDD60091")
      throw new SecurityTokenException("Certificate Validation Error!");
  }
}


当然,配置文件也得做些调整,将 certificateValidationMode 验证改为 Custom 和我们自定义的验证类型。这回不用管它是否是 "不信任证书" 了。(客户端配置不做调整)

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="NewBehavior">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:8080" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceCredentials>
            <clientCertificate>
              <authentication customCertificateValidatorType=
                "Learn.Library.WCF.CustomX509CertificateValidator, Learn.Library"
                certificateValidationMode="Custom" />
            </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="NewBehavior" name="Learn.Library.WCF.MyService">
        <endpoint address="net.tcp://localhost:8081" binding="netTcpBinding"
          bindingConfiguration="NewBinding0" contract="Learn.Library.WCF.IService">
        </endpoint>
      </service>
    </services>
  </system.serviceModel>
</configuration>


代码版

1. 服务器

 
ServiceHost host = new ServiceHost(typeof(MyService), new Uri("net.tcp://localhost:8081"));

NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;
binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;

host.AddServiceEndpoint(typeof(IService), binding, "");

host.Credentials.ServiceCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My,
  X509FindType.FindBySubjectName, "MyServer");
host.Credentials.ClientCertificate.Authentication.CertificateValidationMode =
  X509CertificateValidationMode.None;

host.Open();


2. 客户端1

 
using (ServiceClient client = new ServiceClient())
{
  client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My,
    X509FindType.FindBySubjectName, "Client1");
  client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
    X509CertificateValidationMode.None;
  
  Console.WriteLine(client.Test());
}


app.config

 
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <netTcpBinding>
        <binding name="NetTcpBinding_IService">
          <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_IService"
        contract="ConsoleApplication1.localhost.IService" name="NetTcpBinding_IService">
        <identity>
          <dns value="MyServer" />
        </identity>
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>


3. 客户端2

 
NetTcpBinding binding2 = new NetTcpBinding();
binding2.Security.Mode = SecurityMode.Transport;
binding2.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;
binding2.Security.Transport.ClientCredentialType = TcpClientCredentialType.Certificate;

EndpointAddress endpoint = new EndpointAddress(new Uri("net.tcp://localhost:8081"),
  EndpointIdentity.CreateDnsIdentity("MyServer"));

ChannelFactory<IService> factory = new ChannelFactory<IService>(binding2, endpoint);

factory.Credentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My,
  X509FindType.FindBySubjectName, "Client1");
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
  X509CertificateValidationMode.None;

IService client = factory.CreateChannel();
using (client as IDisposable)
{
  Console.WriteLine(client.Test());
}


注意!在不同机器上测试时,注意设置防火墙参数。

posted @ 2008-08-19 14:33  菩提树下的杨过  阅读(3106)  评论(1编辑  收藏  举报