[WCF安全3]使用wsHttpBinding构建基于SSL与UserName授权的WCF应用程序
上一篇文章中介绍了如何使用wsHttpBinding构建UserName授权的WCF应用程序,本文将为您介绍如何使用wsHttpBinding构建基于SSL的UserName安全授权的WCF应用程序。
与上篇文章一样,同样将该示例分为服务端与客户端介绍。
1. 服务端
(1) 实现CustomUserNameValidator
首先实现CustomUserNameValidator,如何创建CustomUserNameValidator请参见第一篇与第二篇文章。
1 public class CustomUserNameValidator : UserNamePasswordValidator 2 { 3 private const string USERNAME_ELEMENT_NAME = "userName"; 4 5 private const string PASSWORD_ELEMENT_NAME = "password"; 6 7 private const string FAULT_EXCEPTION_MESSAGE = "UserName or Password is incorrect!"; 8 9 public override void Validate(string userName, string password) 10 { 11 Guarder.Guard.ArgumentNotNull(userName) 12 .ArgumentNotNull(password); 13 var validateUserName = ConfigurationManager.AppSettings[USERNAME_ELEMENT_NAME]; 14 var validatePassword = ConfigurationManager.AppSettings[PASSWORD_ELEMENT_NAME]; 15 var validateCondition = userName.Equals(validateUserName) && password.Equals(validatePassword); 16 if (!validateCondition) 17 { 18 throw new FaultException(FAULT_EXCEPTION_MESSAGE); 19 } 20 } 21 }
(2) 注册服务端证书
与上篇文章中一样,首先需要向currentUser或localMachine中注册一个X509证书。
1 makecert.exe -sr CurrentUser -ss My -a sha1 -n CN=ServerCert -sky exchange –pe
(3) 完成服务端配置文件
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <startup> 4 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> 5 </startup> 6 <appSettings> 7 <add key="username" value="username"/> 8 <add key="password" value="password"/> 9 </appSettings> 10 <system.serviceModel> 11 <behaviors> 12 <serviceBehaviors> 13 <behavior name="securityBehavior"> 14 <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /> 15 <serviceDebug includeExceptionDetailInFaults="true" /> 16 <serviceCredentials> 18 <serviceCertificate 19 findValue="ServerCert" 20 x509FindType="FindBySubjectName" 21 storeLocation="CurrentUser" 22 storeName="My"/> 23 <userNameAuthentication 24 userNamePasswordValidationMode="Custom" 25 customUserNamePasswordValidatorType="TimeSynchronizeHttpsServer.CustomUserNameValidator,TimeSynchronizeHttpsServer"/> 26 </serviceCredentials> 27 </behavior> 28 </serviceBehaviors> 29 </behaviors> 30 <bindings> 31 <wsHttpBinding> 32 <binding name="securityMessageBinding"> 33 <security mode="TransportWithMessageCredential"> 34 <transport clientCredentialType="Basic"/> 35 <message clientCredentialType="UserName"/> 36 </security> 37 </binding> 38 </wsHttpBinding> 39 </bindings> 40 <services> 41 <service name="TimeSynchronizeHttpsServer.TimeSynchronizeService" behaviorConfiguration="securityBehavior"> 44 <endpoint address="https://127.0.0.1:12218/TimeSynchronize" 45 binding="wsHttpBinding" bindingConfiguration="securityMessageBinding" 46 contract="TimeSynchronizeHttpsServer.ITimeSynchronizeService"> 47 <identity> 48 <dns value="localhost" /> 49 </identity> 50 </endpoint> 51 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> 52 <host> 53 <baseAddresses> 54 <add baseAddress="http://localhost:8733/Design_Time_Addresses/TimeSynchonizeHttpsServer/TimeSynchronizeService/" /> 55 </baseAddresses> 56 </host> 57 </service> 58 </services> 59 </system.serviceModel> 60 </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证书上。
1 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见鬼去吧,哈哈。
1 class CertificateValidator 2 { 3 public static void SetCertificatePolicy ( ) 4 { 5 ServicePointManager.ServerCertificateValidationCallback 6 += RemoteCertificateValidate; 7 } 8 private static bool RemoteCertificateValidate ( object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error ) 9 { 10 System.Console.WriteLine ( "Warning, trust any certificate" ); 11 return true; 12 } 13 }
在客户端调用代码时,调用客户端的Contract之前,调用CertificateValidator.SetCertificatePolicy()方法。
(2) 完成客户端配置文件
1 <?xml version="1.0" encoding="utf-8" ?> 2 <configuration> 3 <startup> 4 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> 5 </startup> 6 <appSettings> 7 <add key="username" value="username"/> 8 <add key="password" value="password"/> 9 </appSettings> 10 <system.serviceModel> 11 <bindings> 12 <wsHttpBinding> 13 <binding name="securityMixedBinding"> 14 <security mode="TransportWithMessageCredential"> 15 <transport clientCredentialType="Basic"/> 16 <message clientCredentialType="UserName"/> 17 </security> 18 </binding> 19 </wsHttpBinding> 20 </bindings> 21 <client> 22 <endpoint address="https://127.0.0.1:12218/TimeSynchronize" 23 binding="wsHttpBinding" bindingConfiguration="securityMixedBinding" 24 contract="ITimeSynchronizeService" 25 name="DefaultBinding_ITimeSynchronizeService_ITimeSynchronizeService" /> 26 </client> 27 </system.serviceModel> 28 </configuration>
需要注意的是,binding节点需要与服务端配置文件中的binding配置相同。endpoint的地址与service相同,都要采用https基地址。
(3) 实现客户端调用代码
1 private const string USERNAME = "userName"; 2 private const string PASSWORD = "password"; 3 4 static void Main ( string[] args ) 5 { 6 var proxy = new TimeSynchronizeServiceClient ( ); 7 var userName = ConfigurationManager.AppSettings[USERNAME]; 8 var password = ConfigurationManager.AppSettings[PASSWORD]; 9 proxy.ClientCredentials.UserName.UserName = userName; 10 proxy.ClientCredentials.UserName.Password = password; 11 CertificateValidator.SetCertificatePolicy ( ); 12 var time = proxy.GetTime ( ); 13 var builder = new StringBuilder ( ); 14 builder.Append ( "Server time is:" ).Append ( " " ).Append ( time ); 15 var message = builder.ToString ( ); 16 Console.WriteLine ( message ); 17 Console.ReadKey ( ); 18 }
在调用客户端的Contract之前,需要调用CertificateValidator.SetCertificatePolicy()方法。
OK,打完收工。