[WCF安全3]使用wsHttpBinding构建基于SSL与UserName授权的WCF应用程序

时间:2022-01-15 12:52:00

上一篇文章中介绍了如何使用wsHttpBinding构建UserName授权的WCF应用程序,本文将为您介绍如何使用wsHttpBinding构建基于SSL的UserName安全授权的WCF应用程序。

  与上篇文章一样,同样将该示例分为服务端与客户端介绍。

  1. 服务端

  (1) 实现CustomUserNameValidator

  首先实现CustomUserNameValidator,如何创建CustomUserNameValidator请参见第一篇第二篇文章。

 public class CustomUserNameValidator : UserNamePasswordValidator
{
  private const string USERNAME_ELEMENT_NAME = "userName"; private const string PASSWORD_ELEMENT_NAME = "password"; private const string FAULT_EXCEPTION_MESSAGE = "UserName or Password is incorrect!"; public override void Validate(string userName, string password)
{
  Guarder.Guard.ArgumentNotNull(userName)
.ArgumentNotNull(password);
var validateUserName = ConfigurationManager.AppSettings[USERNAME_ELEMENT_NAME];
var validatePassword = ConfigurationManager.AppSettings[PASSWORD_ELEMENT_NAME];
var validateCondition = userName.Equals(validateUserName) && password.Equals(validatePassword);
if (!validateCondition)
{
  throw new FaultException(FAULT_EXCEPTION_MESSAGE);
}
  }
}

  (2) 注册服务端证书

  与上篇文章中一样,首先需要向currentUser或localMachine中注册一个X509证书。

 makecert.exe -sr CurrentUser -ss My -a sha1 -n CN=ServerCert -sky exchange –pe

  (3) 完成服务端配置文件

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="username" value="username"/>
<add key="password" value="password"/>
</appSettings>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="securityBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate
findValue="ServerCert"
x509FindType="FindBySubjectName"
storeLocation="CurrentUser"
storeName="My"/>
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="TimeSynchronizeHttpsServer.CustomUserNameValidator,TimeSynchronizeHttpsServer"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="securityMessageBinding">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="Basic"/>
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="TimeSynchronizeHttpsServer.TimeSynchronizeService" behaviorConfiguration="securityBehavior">
<endpoint address="https://127.0.0.1:12218/TimeSynchronize"
binding="wsHttpBinding" bindingConfiguration="securityMessageBinding"
contract="TimeSynchronizeHttpsServer.ITimeSynchronizeService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8733/Design_Time_Addresses/TimeSynchonizeHttpsServer/TimeSynchronizeService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>

  其中serviceCredentials节点中添加了serviceCertificate节点,该节点用来指定使用的证书。findValue,此attribute指定的是(2)中命令行中CN=右侧的字符。userNameAuthentication制定了自定义的Validator。binding节点中使用了wsHttpBinding,并将传输的加密方式指定为TransportWithMessageCredential,并制定Message的授权方式为UserName,Transport的授权方式为Basic。endpoint的地址需要使用https协议作为基地址的头部。

  (4) 将服务端应用程序绑定到SSL证书上

  如果你的WCF的承载方式为self-host(所谓self-host方式,指将WCF服务host在一个windows外壳中,包括winform、wpf、windowService。这种方式是相对于IIS承载来说的,IIS承载方式网上介绍的例子已经很多了,这篇文章主要介绍self-host方式。),需要手动将你的外壳程序绑定到SSL证书上。

  首先需要使用(2)中的方式在LocalMachine中建立一个证书,注意,一定是LocalMachine中。然后,使用下面的命令行代码,将你的服务端外壳程序绑定到SSL证书上。

 netsh http add sslcert ipport=0.0.0.0:12218 certhash=1dca86867481b22c8f15a134df62af649cc3343a clientcertnegotiation=enable appid={02639d71-0935-35e8-9d1b-9dd1a2a34627}

  其中,ipport的值中的0.0.0.0代表本地地址,冒号后面代表端口;certhash的值为证书的thumbPrint值,怎么找到这个值呢?follow this article吧,哈哈,英文的,z正好提高一下你的英语水平,哈哈。appid中的值是程序的guid,在项目属性的assemblyInfo中可以找到。

  2. 客户端

  (1) 实现CertificateValidator

  这个是在第二篇文章中没有的,这个Validator干啥用的呢。这个东西是因为我们的证书都是自己颁发给给自己的,所以在安全性上存在一些问题,如果我们用根证书颁发机构颁发的证书,让这个Validator见鬼去吧,哈哈。

 class CertificateValidator
{
  public static void SetCertificatePolicy ( )
  {
    ServicePointManager.ServerCertificateValidationCallback
+= RemoteCertificateValidate;
  }
  private static bool RemoteCertificateValidate ( object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error )
  {
    System.Console.WriteLine ( "Warning, trust any certificate" );
  return true;
  }
}

  在客户端调用代码时,调用客户端的Contract之前,调用CertificateValidator.SetCertificatePolicy()方法。

  (2) 完成客户端配置文件

 <?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="username" value="username"/>
<add key="password" value="password"/>
</appSettings>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="securityMixedBinding">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="Basic"/>
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://127.0.0.1:12218/TimeSynchronize"
binding="wsHttpBinding" bindingConfiguration="securityMixedBinding"
contract="ITimeSynchronizeService"
name="DefaultBinding_ITimeSynchronizeService_ITimeSynchronizeService" />
</client>
</system.serviceModel>
</configuration>

  需要注意的是,binding节点需要与服务端配置文件中的binding配置相同。endpoint的地址与service相同,都要采用https基地址。

  (3) 实现客户端调用代码

 private const string USERNAME = "userName";
private const string PASSWORD = "password"; static void Main ( string[] args )
{
  var proxy = new TimeSynchronizeServiceClient ( );
var userName = ConfigurationManager.AppSettings[USERNAME];
var password = ConfigurationManager.AppSettings[PASSWORD];
proxy.ClientCredentials.UserName.UserName = userName;
proxy.ClientCredentials.UserName.Password = password;
CertificateValidator.SetCertificatePolicy ( );
var time = proxy.GetTime ( );
var builder = new StringBuilder ( );
builder.Append ( "Server time is:" ).Append ( " " ).Append ( time );
var message = builder.ToString ( );
Console.WriteLine ( message );
Console.ReadKey ( );
}

  在调用客户端的Contract之前,需要调用CertificateValidator.SetCertificatePolicy()方法。

  OK,打完收工。