[WCF Security] 3. X509 身份验证

个人认为在 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 @ 2011-05-20 22:01  芙蓉客  阅读(233)  评论(0)    收藏  举报