[WCF Security] 3. X509 身份验证
个人认为在 Intranet / Internet 环境下,最方便的认证方式应该是 X.509 数字证书。当然,还有一个原因是我用 Windows Authentication 从来没成功过。
以下我们详细描述如何创建 "Certificate Authentication"。
1. 创建数字证书
一般情况下,我们为服务器以及每个客户端都单独创建一个服务器,以便标识其唯一身份。创建数字证书时,必须添加 "-pe" 和 "-sky exchange" 参数。有关数字证书更多的信息,请参考《X.509 & RSA》。
2. 创建服务
安全方式:Transport
客户端验证类型:Certificate
在 serviceCredentials 中设置好证书的查找参数,同时将验证模式(certificateValidationMode) 设为 None (因为我们创建的是 "不受信任" 的证书)。
3. 创建客户端
生成代理客户端。
注意在配置文件中,我们在 EndPointBehaviors 中添加 ClientCredentials 来设置数字整数的相关信息。基于和上面同样的理由,我们也得将 certificateValidationMode 设为 "None"。
运行后,我们对比一下服务器端输出结果和Client1数字证书的信息。
每个客户端的数字证书名称和序号的组合都是唯一的,我们通过它做出相应的验证动作。
4. 验证器
WCF 提供了多种数字证书的验证手段,不过我们最习惯的应该还是 "Custom",因为有很多附加行为要处理。
在服务器端添加一个继承自 X509CertificateValidator 的验证器。
当然,配置文件也得做些调整,将 certificateValidationMode 验证改为 Custom 和我们自定义的验证类型。这回不用管它是否是 "不信任证书" 了。(客户端配置不做调整)
代码版
1. 服务器
2. 客户端1
app.config
3. 客户端2
注意!在不同机器上测试时,注意设置防火墙参数。
以下我们详细描述如何创建 "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());
}
注意!在不同机器上测试时,注意设置防火墙参数。


浙公网安备 33010602011771号