Introduction
This is a wizard to lead you to complete the WCF service & client base on wsHttpBinding
without certificate.
Background
I take the order to investigate the WCF authentication with username. So I found out about basicHttpBinding
& wsHttpBinding
. In this tip, I will show you how to use wsHttpBinding
with SSL to communicate with authentication WCF service application.
And you may know how to build a WCF application. So, this page is not pointing to how to build a WCF application, but points to how to build a custom username validator & how to build the configuration file.
Using the Code
There are two parts of the application, of which one is the service part, and the other is the client part.
In the server part, you could build a class named CustomUserNameValidator
and inherit UserNamePasswordValidator
. And implement the abstract
method "Validate
". And you can throw some exception when the username and password are invalidated.
The Service Work
- You should complete the validator code.
- You should make a certificate for the transport encryption, and you can follow this article.
- You should make a certificate for the SSL cert, and you can follow this article.
- Bind you service application to the SSL.
- Finish your service configuration.
The Validator Code
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);
}
}
}
Make the Certificate of your Transport Encryption
makecert.exe -sr LocalMachine -ss My -a sha1 -n CN=ServerCert -sky exchange –pe
Bind your Application to the SSL Code
netsh http add sslcert ipport=0.0.0.0:12221 certhash=32e636c3cefb16726b88b082b22b51194d77f865
appid={ba77e2bd-7499-44ee-bfb0-b45188f94e7f} clientcertnegotiation=enable
In the code, certhash
is the thumbprint of the certificate, you can get it in the mms application, which is the native tool of Windows. And the appid
, you can get it from the assembly information of the project.
And the Service Configuration
="1.0"="utf-8"
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="username" value="your username"/>
<add key="password" value="your 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:12221/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>
The Client Work
- Finish the client configuration
- Complete the client custom validator
- Complete the client code
Finish the Client Configuration
The client configuration is like below:
="1.0"="utf-8"
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="username" value="your username"/>
<add key="password" value="your 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:12221/TimeSynchronize"
binding="wsHttpBinding" bindingConfiguration="securityMixedBinding"
contract="ITimeSynchronizeService"
name="DefaultBinding_ITimeSynchronizeService_ITimeSynchronizeService" />
</client>
</system.serviceModel>
</configuration>
The Client Custom 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;
}
}
The validator's usage is to ensure the remote certificate validate. Because the certificate is published by ourselves, so we should cheat the server to get passed. And we should call the method "SetCertificatePolicy
" before we call the contract.
The Client Application Code
class Program
{
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 ( );
}
}
Result
I searched a lot of documents on the internet, and modified some demos by myself. And this document can be passed when I run the application. I hope this page is useful for you.