WCF Authentication - Username-Password using custom validator - Works on My Machine
I have a WCF service and a client, that uses a custom UserNamePasswordValidator
for authentication.
I use a self-signed certificate, created by the following command line:
makecert -sr LocalMachine -ss My -a sha1 -n CN=SelfSignedCertificate -sky exchange -pe
Calling methods in the service from the below client works fine on my machine :) But as soon as I deploy it to my server, I get this error message: The request for security token could not be satisfied because authentication failed.
I can browse the endpoint URL and see the WSDL for the service. I'm not sure, but I recall that I configured some Anonymous Authentication in IIS on my local machine, but it looks similar on the server as well.
My WCF service is hosted in IIS 7.0, using this configuration:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="secured">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
<binding name="unsecured">
<security mode="None" />
</binding>
</wsHttpBinding>
<basicHttpBinding>
<binding name="secured">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
<binding name="unsecured">
<security mode="None" />
</binding>
</basicHttpBinding>
<webHttpBinding>
<binding name="unsecured">
<security mode="None" />
</binding>
</webHttpBinding>
</bindings>
<services>
<service name="Milkshake.Admin.Services.AdminService" behaviorConfiguration="CustomValidator">
<endpoint address="" binding="wsHttpBinding" contract="Milkshake.Admin.Model.ServiceContracts.IAdminService" bindingConfiguration="secured" />
</service>
<service name="Milkshake.Admin.Services.DeploymentService">
<endpoint address="" binding="wsHttpBinding" contract="Milkshake.Admin.Model.ServiceContracts.IDeploymentService"/>
</service>
<service name="Milkshake.Admin.Services.LogService" behaviorConfiguration="CustomValidator">
<endpoint address="" binding="wsHttpBinding" contract="Milkshake.Core.Logging.ILogService" bindingConfiguration="secured" />
</service>
<service name="Milkshake.Admin.Services.MailService">
<endpoint address="" binding="wsHttpBinding" contract="Milkshake.Admin.Model.ServiceContracts.IMailService"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="CustomValidator">
<serviceMetadata httpGetEnabled="true" />
<serviceCredentials>
<serviceCertificate findValue="SelfSignedCertificate" x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Milkshake.Admin.Services.MilkshakeCredentialValidator, Milkshake.Admin.Services" />
<clientCertificate>
<authentication certificateValidationMode="None" />
</clientCertificate>
</serviceCredentials>
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="json">
<enableWebScript />
</behavior>
<behavior name="xml">
<webHttp defaultOutgoingResponseFormat="Xml" defaultBodyStyle="Wrapped" />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
The client configuration looks like this:
<system.serviceModel>
<client>
<endpoint address="http://server-url/LogService.svc" binding="wsHttpBinding" contract="Milkshake.Core.Logging.ILogService">
<identity>
<dns value="SelfSignedCertificate" />
</identity>
</endpoint>
</client>
</system.serviceModel>
My custom validator is this:
using System;
using System.IdentityModel.Selectors;
using System.ServiceModel;
namespace Milkshake.Admin.Services
{
/// <summary>
/// WCF Service validator for Milkshake.
/// </summary>
public class MilkshakeCredentialValidator : UserNamePasswordValidator
{
/// <summary>
/// When overridden in a derived class, validates the specified username and password.
/// </summary>
/// <param name="userName">The username to validate.</param>
/// <param name="password">The password to validate.</param>
public override void Validate(string userName, string password)
{
if (String.IsNullOrWhiteSpace(userName) || String.IsNullOrWhiteSpace(password))
{
throw new ArgumentNullException();
}
if (userName.Equals("martin") && password.Equals("normark"))
{
return;
}
FaultCode fc = new FaultCode("ValidationFailed");
FaultReason fr = new FaultReason("Good reason");
throw new FaultException(fr, fc);
}
}
}
My service client, looks like this:
using System;
using System.ServiceModel;
using System.ServiceModel.Security;
using Milkshake.Core.Logging;
namespace Milkshake.Admin.ServiceClients.Logging
{
/// <summary>
/// WCF Service Client implementation of the <see cref="ILogService"/> contract.
/// </summary>
public class LogServiceClient : ILogService
{
/// <summary>
/// Initializes a new instance of the <see cref="LogServiceClient"/> class.
/// </summary>
public LogServiceClient()
{
var factory = new ChannelFactory<ILogService>(String.Empty);
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
factory.Credentials.UserName.UserName = "martin";
factory.Credentials.UserName.Password = "normark";
var binding = factory.Endpoint.Binding as WSHttpBinding;
if (binding != null)
{
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
}
this.AdminLogService = factory.CreateChannel();
}
/// <summary>
/// Gets or sets the admin log service.
/// </summary>
/// <value>The admin log service.</value>
private ILogService AdminLogService { get; set; }
#region ILogService Members
/// <summary>
/// Logs the error simple.
/// </summary>
/// <param name="applicationInstanceId">The application instance id.</param>
/// <param name="logDate">The log date.</param>
/// <param name="exceptionMessage">The exception message.</param>
/// <param name="description">The description.</param>
/// <param name="severity">The severity.</param>
/// <param name="userAgent">The user agent.</param>
/// <param name="source">The source.</param>
public void LogErrorSimple(int applicationInstanceId, DateTime logDate, string exceptionMessage, string description, Severity severity, string userAgent, string source)
{
this.AdminLogService.LogErrorSimple(applicationInstanceId, logDate, exceptionMessage, description, severity, userAgent, source);
}
/// <summary>
/// Logs the error advanced.
/// </summary>
/// <param name="applicationInstanceId">The applica开发者_开发百科tion instance id.</param>
/// <param name="logDate">The log date.</param>
/// <param name="exceptionType">Type of the exception.</param>
/// <param name="exceptionCode">The exception code.</param>
/// <param name="exceptionMessage">The exception message.</param>
/// <param name="stackTrace">The stack trace.</param>
/// <param name="caller">The caller.</param>
/// <param name="location">The location.</param>
/// <param name="source">The source (A service, app etc).</param>
/// <param name="description">The description.</param>
/// <param name="severity">The severity.</param>
/// <param name="username">The username.</param>
/// <param name="userAgent">The user agent.</param>
public void LogErrorAdvanced(int applicationInstanceId, DateTime logDate, string exceptionType, string exceptionCode, string exceptionMessage, string stackTrace, string caller, string location, string source, string description, Severity severity, string username, string userAgent)
{
this.AdminLogService.LogErrorAdvanced(applicationInstanceId, logDate, exceptionType, exceptionCode, exceptionMessage, stackTrace, caller, location, source, description, severity, userAgent, userAgent);
}
/// <summary>
/// Logs the behavior with data.
/// </summary>
/// <param name="applicationInstanceId">The application instance id.</param>
/// <param name="action">The action.</param>
/// <param name="logDate">The log date.</param>
/// <param name="userAgent">The user agent.</param>
/// <param name="behaviorData">The behavior data.</param>
/// <param name="source">The source.</param>
public void LogBehaviorWithData(int applicationInstanceId, string action, DateTime logDate, string userAgent, string behaviorData, string source)
{
this.AdminLogService.LogBehaviorWithData(applicationInstanceId, action, logDate, userAgent, behaviorData, source);
}
/// <summary>
/// Logs the behavior.
/// </summary>
/// <param name="applicationInstanceId">The application instance id.</param>
/// <param name="action">The action.</param>
/// <param name="logDate">The log date.</param>
/// <param name="userAgent">The user agent.</param>
/// <param name="source">The source.</param>
public void LogBehavior(int applicationInstanceId, string action, DateTime logDate, string userAgent, string source)
{
this.AdminLogService.LogBehavior(applicationInstanceId, action, logDate, userAgent, source);
}
#endregion
}
}
I also use the UserNamePasswordValidator with certificates generated using makecert.
Instead of using IIS, use WcfSvcHost so you can be sure it starts successfully. The reason I'm suggesting this is that possibly Chain building for the certificate is failing. There is a setting you can use to disable chain building. (Update: I think you're doing that already with CertificateValidationMode="none")
Another thing I'm noticing is that not all your service definitions specify behaviourConfiguration. This is a little beech to find because it gets removed sometimes by VS when you update your service references.
The exception you mention, is that the message from the inner most exception?
精彩评论