原文: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数字证书的信息。
每个客户端的数字证书名称和序号的组合都是唯一的,我们通过它做出相应的验证动作。
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());
}
注意!在不同机器上测试时,注意设置防火墙参数。