上一篇文章中介绍了如何使用basicHttpBinding构建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) 注册服务端证书
这个环节是在上一篇文章中没有的,后文中要介绍到,我们使用了Message的方式加密消息内容,因此,我们需要将传输内容加密,因此需要使用证书进行加密。我们使用微软自带的"makecert.exe"命令行工具向currentUser注册一个证书。使用Visual Studio自带的命令行工具执行下面命令行。
makecert.exe -sr CurrentUser -ss My -a sha1 -n CN=ServerCert -sky exchange –pe
(3) 完成服务端配置文件
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="username" value="username"/>
<add key="password" value="password"/>
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<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="TimeSynchronizeServer.CustomUserNameValidator,TimeSynchronizeServer"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="securityMessageBinding">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="TimeSynchronizeServer.TimeSynchronizeService"
behaviorConfiguration="securityBehavior">
<endpoint address="http://127.0.0.1/12216/TimeSynchronize"
binding="wsHttpBinding" bindingConfiguration="securityMessageBinding"
contract="TimeSynchronizeServer.ITimeSynchronizeService">
<identity>
<dns value="ServerCert" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8733/Design_Time_Addresses/TimeSynchronizeServer/TimeSynchronizeService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
其中serviceCredentials节点中添加了serviceCertificate节点,该节点用来指定使用的证书。findValue,此attribute指定的是(2)中命令行中CN=右侧的字符。userNameAuthentication制定了自定义的Validator。binding节点中使用了wsHttpBinding,并将传输的加密方式指定为Message,并制定UserName为授权方式。
2. 客户端
(1) 完成客户端配置文件
<?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="securityMessageBidning">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://127.0.0.1/12216/TimeSynchronize/"
binding="wsHttpBinding" bindingConfiguration="securityMessageBidning"
contract="ITimeSynchronizeService" name="DefaultBinding_ITimeSynchronizeService_ITimeSynchronizeService">
<identity>
<dns value="ServerCert"/>
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
需要注意的是,binding节点需要与服务端配置文件中的binding配置相同。特别要注意的是,endpoint节点中的identity节点中的dns的value需要与证书名称相同,否则会抛出一个异常,具体问题我还没搞明白,不过搜索了一下,发现这样能够解决问题。
(2) 完成客户端调用代码
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;
proxy.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.None;
var time = proxy.GetTime ( );
var builder = new StringBuilder ( );
builder.Append ( "Server time is:" ).Append ( " " ).Append ( time );
var message = builder.ToString ( );
Console.WriteLine ( message );
18 Console.ReadKey ( );
}
OK,大功告成!