I have to make a centralized log repository and I decided to mount a WCF service implementing NLog's LogReceiverService (through wsHttpBinding). I followed this topic where I found a working example (there is a working code at bitbucket).
Ok, now the problem: I would like to add some security to this WCF Service, expose it through HTTPS and maybe add an Authentication Token. I have programmed this kind of authentication earlier, so I do know how to do it, it's just I don't know how can I program that within NLog. Should I modify the Class where NLog makes the call to the WCF Method? I just can't picture how to do it. Any ideas about how to achieve this functionality is really appreciated.
Finally I was able to do this.
Let me tell you I was able to configure the desired behavior :)
First we configure the server as follows:
The configuration of System.ServiceModel for the web.config of the WCFService is:
<system.serviceModel>
<services>
<service name="Your.Namespace.Path.To.Your.Service" behaviorConfiguration="SecureBehavior">
<endpoint binding="wsHttpBinding" bindingConfiguration="SecureBinding" contract="NLog.LogReceiverService.ILogReceiverServer"/>
<endpoint binding="mexHttpBinding" contract="IMetadataExchange" address="mex"/>
<host>
<baseAddresses>
<add baseAddress="https://your_secure_domain.com/servicePath"/>
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="SecureBehavior">
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceMetadata httpsGetEnabled="true"/>
<serviceCredentials>
<!--You must set your certificate configuration to make this example work-->
<serviceCertificate findValue="0726d1969a5c8564e0636f9eec83f92e" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySerialNumber"/>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="AssamblyOf.YourCustom.UsernameValidator.UsernameValidator, AssamblyOf.YourCustom.UsernameValidator"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="SecureBinding" closeTimeout="00:00:20" openTimeout="00:00:20" receiveTimeout="00:00:20" sendTimeout="00:00:20">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
The CustomUserNameValidator
public class UsernameValidator : UserNamePasswordValidator
{
private const string UserName = "your_username_here";
private const string Password = "your_password_here";
public override void Validate(string userName, string password)
{
// validate arguments
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName");
if (string.IsNullOrEmpty(password))
throw new ArgumentNullException("password");
//
// Nombre de usuario y contraseñas hardcodeados por seguridad
//
if (!userName.Equals(UserName) || !password.Equals(Password))
throw new SecurityTokenException("Nombre de usuario o contraseña no válidos para consumir este servicio");
}
}
then we go to the Client configuration
First, create a inherited class from LogReceiverWebServiceTarget and I override the method CreateWcfLogReceiverClient, then in that method add the credentials.
// we assume that this class is created in NLog.CustomExtendedService namespace
[Target("LogReceiverSecureService")]
public class LogReceiverSecureService : NLog.Targets.LogReceiverWebServiceTarget
{
/// <summary>
/// Gets or sets the UserName of the service when it's authentication is set to UserName
/// </summary>
/// <value>The name of the endpoint configuration.</value>
public string ServiceUsername { get; set; }
/// <summary>
/// Gets or sets de Password of the service when it's authentication is set to UserName
/// </summary>
public string ServicePassword { get; set; }
/// <summary>
/// Creates a new instance of WcfLogReceiverClient.
///
/// We make override over this method to allow the authentication
/// </summary>
/// <returns></returns>
protected override NLog.LogReceiverService.WcfLogReceiverClient CreateWcfLogReceiverClient()
{
var client = base.CreateWcfLogReceiverClient();
if (client.ClientCredentials != null)
{
//
// You could use the config file configuration (this example) or you could hard-code it (if you do not want to expose the credentials)
//
client.ClientCredentials.UserName.UserName = this.ServiceUsername;
client.ClientCredentials.UserName.Password = this.ServicePassword;
}
return client;
}
}
Then we set up the application's config file
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ILogReceiverServer">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
<transport clientCredentialType="None" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://your_secure_domain.com/servicePath/Logger.svc" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_ILogReceiverServer" contract="NLog.LogReceiverService.ILogReceiverClient"
name="WSHttpBinding_ILogReceiverServer" />
</client>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
Finally we configure the NLog.config
<extensions>
<add assembly="NLog.CustomExtendedService" /> <!--Assuming the custom Target was added to this assambly -->
</extensions>
<targets>
<target xsi:type="LogReceiverSecureService"
name="RemoteWcfLogger"
endpointConfigurationName="WSHttpBinding_ILogReceiverServer"
endpointAddress="https://your_secure_domain.com/servicePath/Logger.svc"
ServiceUsername="your_username_here"
ServicePassword="your_password_here"
useBinaryEncoding="True"
clientId="YourApplicationNameOrId"
includeEventProperties="True">
</target>
</targets>
I posted an entire answer at the googlegroup of NLog, so enjoy it
https://groups.google.com/d/msg/nlog-users/Xryu61TaZKM/Utbvrr5mwA0J
Related
I am using a DuplexChannelFactory to connect my client to my server and what I want to do now is connect the server to each client that will register on the server. But every time I launch a client, my "init()" function is ran twice. And my DuplexChannelFactory on the server side doesn't work.
Here is how I open my server for clients :
host = new ServiceHost(typeof(NewServeur.NewServeur));
host.Open();
with its App.Config :
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MEX">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="MEX" name="NewServeur.NewServeur">
<endpoint address="IProxyNewServeur" binding="netTcpBinding"
bindingConfiguration="configWin" name="NetTcpEndpoint" contract="ProxyNewServeur.IProxyNewServeur" />
<endpoint address="MEX" binding="mexTcpBinding" bindingConfiguration=""
name="MexTcpEndpoint" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://192.156.142.176:8080/" />
</baseAddresses>
</host>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="configWin">
<security mode="None"></security>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
Here is how I connect my client to my server :
void init()
{
ServiceCablingCallback ServiceCablingCallback = new ServiceCablingCallback();
ServiceCablingCallback.ClientNotified += ServiceCablingCallback_ClientNotified;
this._instanceContext = new InstanceContext(ServiceCablingCallback);
this.factory = new DuplexChannelFactory<ProxyNewServeur.IProxyNewServeur>(_instanceContext, "configClient");
this._serviceCablingClient = factory.CreateChannel();
this._clientId = _serviceCablingClient.Subscribe();
}
with its App.Config
<system.serviceModel>
<client>
<endpoint address="net.tcp://192.156.142.176:8080/IProxyNewServeur"
binding="netTcpBinding" bindingConfiguration="configWin" contract="ALSTEF.Cabling.ProxyNewServeur.IProxyNewServeur"
name="configClient" />
</client>
<bindings>
<netTcpBinding>
<binding name="configWin">
<security mode="None" />
</binding>
</netTcpBinding>
</bindings>
This works fine, but now I want to do the reverse thing, meaning connecting the server to the client. In order to do so, I added those lines before the last line of my client :
host = new ServiceHost(typeof(Service), new Uri(_serviceCablingClient.getIp()));
host.Open();
with getIp() returning the Ip address + the port 4242 of my client.
and this to the client App.config
<behaviors>
<serviceBehaviors>
<behavior name="MEX">
<serviceMetadata />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="MEX" name="Client.Model.Service">
<endpoint address="IClientFunction" binding="netTcpBinding" bindingConfiguration="ClientConfig"
name="NetTcpEndpoint" contract="ProxyNewServeur.IClientFunction" />
<endpoint address="MEX" binding="mexTcpBinding" bindingConfiguration=""
name="MexTcpEndpoint" contract="IMetadataExchange" />
</service>
</services>
<!-- in the bindings html tag -->
<binding name="ClientConfig">
<security mode="None" />
</binding>
And I modified the _serviceCablingClient.Subscribe() by adding those lines (so in the server)
public Guid Subscribe()
{
ProxyNewServeur.INewServeurCallback callback =
OperationContext.Current.GetCallbackChannel<ProxyNewServeur.INewServeurCallback>();
System.ServiceModel.Channels.RemoteEndpointMessageProperty endpointProperty =
OperationContext.Current.IncomingMessageProperties[System.ServiceModel.Channels.RemoteEndpointMessageProperty.Name] as System.ServiceModel.Channels.RemoteEndpointMessageProperty;
Guid clientId = Guid.NewGuid();
if (callback != null)
{
lock (clientsCallback)
{
clientsCallback.Add(clientId, callback);
}
}
/* code I added */
string addr = "net.tcp://" + endpointProperty.Address + ":" + "4242" + "/IClientFunction";
ClientCallback cb = new ClientCallback();
var Okvp = new KeyValuePair<Guid, DuplexChannelFactory<ProxyNewServeur.IClientFunction>>(clientId,
new DuplexChannelFactory<ProxyNewServeur.IClientFunction>(new InstanceContext(cb), "configServer",
new EndpointAddress(addr)));
this.DictClient.Add(Okvp, Okvp.Value.CreateChannel());
this.DictClient[Okvp].CreateCallbackChannel();
/* end code I added */
}
And I added this to the server App.config
<client>
<endpoint binding="netTcpBinding" bindingConfiguration="ClientConfig" contract="ProxyNewServeur.IClientFunction" name="configServer" />
</client>
<!-- in the bindings html tag -->
<binding name="ClientConfig">
<security mode="None" />
</binding>
Here is how I define my ProxyServeur file
namespace ProxyNewServeur
{
[ServiceContract(CallbackContract = typeof(INewServeurCallback))]
public interface IProxyNewServeur
{
[OperationContract]
Guid Subscribe();
[OperationContract]
string getIp();
}
[ServiceContract]
public interface INewServeurCallback
{
[OperationContract(IsOneWay = true)]
void HandleMessage(Message message);
}
[ServiceContract(CallbackContract = typeof(IClientCallback))]
public interface IClientFunction
{
[OperationContract(IsOneWay = true)]
void CreateCallbackChannel();
}
[ServiceContract]
public interface IClientCallback
{
[OperationContract(IsOneWay = true)]
void BroadcastToServer(bool isActive, Guid ClientId);
}
}
Now When I launch a client the line
DictClient[Okvp].CreateCallbackChannel();
won't work and I will go twice in the
init()
{ /* code*/
}
function.
I have no idea why this is happening. I did the exact same thing in a test project and it worked fine.
EDIT
As it turns out, you cannot have a DuplexChannelFactory<> and a ServiceHost in the same method. So what I did is that I added a new Class "DoubleConnection" that have only one attribute.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DoubleConnection : ProxyNewServeur.IClientFunction
{
public ServiceHost host;
public void CreateCallbackChannel()
{}
public void isActive()
{}
}
and in the init() function I removed
host = new ServiceHost(typeof(Service), new Uri(_serviceCablingClient.getIp()));
host.Open();
and added
varClass = new DoubleConnection();
varClass.host = new ServiceHost(typeof(DoubleConnection), new Uri(_serviceCablingClient.getIp()));
varClass.host.Open();
I works fine now but I still don't know why you cannot have a DuplexChannelFactory<> and a ServiceHost in the same method.
I have a Windows Service which represents the WCF-Host and a WPF-Client-Application wich represents the WCF-Client.
The communication should be duplex so I went with WSDualHttpBinding.
At first I install and start my Service which opens a WCF connection after that I start my WPF app and I get the following error (I translated it):
No default endpoint was found to the contract
\ " WCFCloudManagerFolderWatcherService.Interfaces.IFilesDuplex \ " in the service model client configuration section
refers. This may be caused by: For the purposes of no configuration file was found
or in the client element no endpoint element was found , which corresponded to this contract .
Contracts:
IFilesDuplex-Contract:
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode = SessionMode.Required,
CallbackContract = typeof(IFilesDuplexCallback))]
public interface IFilesDuplex
{
[OperationContract(IsOneWay = true)]
void Update();
}
IFilesDuplexCallback:
interface IFilesDuplexCallback
{
[OperationContract(IsOneWay = true)]
void Equals(string[] result);
}
ClientSide
CallbackHandler:
class CallbackHandler : IFilesDuplexCallback
{
public event Action<string[]> ReceivedList = delegate { };
public void Equals(string[] result)
{
this.ReceivedList(result);
}
}
The Client itself:
class FilesDuplexClient : DuplexClientBase<IFilesDuplex>, IFilesDuplex
{
public FilesDuplexClient(InstanceContext callbackCntx)
: base(callbackCntx)
{
}
public void Update()
{
base.Channel.Update();
}
}
And the Code from the Main Window, where the error is thrown:
CallbackHandler ch = new CallbackHandler();
ch.ReceivedList += ch_ReceivedList;
// Construct InstanceContext to handle messages on callback interface
InstanceContext instanceContext = new InstanceContext(ch);
// Create a client
FilesDuplexClient client = new FilesDuplexClient(instanceContext);
client.Update();
Serverside (Windows Service)
FileProtocoll-Class (Server code)
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class FileProtocoll : IFilesDuplex
{
IFilesDuplexCallback Callback
{ get { return OperationContext.Current.GetCallbackChannel<IFilesDuplexCallback>(); } }
void IFilesDuplex.Update()
{
//....
Callback.Equals(null);// just a dummy
//...
}
}
Code in the OnStart-Method (in a Thread):
// Step 1 Create a URI to serve as the base address.
Uri baseAddress = new Uri("http://localhost:8899/CloudManager/CommunicationChannel1");
// Step 2 Create a ServiceHost instance
if (selfHost != null)
{
selfHost.Close();
}
selfHost = new ServiceHost(typeof(FileProtocoll), baseAddress);
try
{
// Step 5 Start the service.
selfHost.Open();
}
catch (CommunicationException ce)
{
selfHost.Abort();
}
Code in the OnStop-Method (in a Thread):
if (selfHost != null)
{
if (selfHost.State != CommunicationState.Closed)
{
selfHost.Close();
}
selfHost = null;
}
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
<system.serviceModel>
<services >
<service behaviorConfiguration="ServiceBehavior"
name="WCFCloudManagerFolderWatcherService.Communication.FileProtocoll">
<endpoint address="http://localhost:8899/CloudManager /CommunicationChannel1"
binding="wsDualHttpBinding" contract="WCFCloudManagerFolderWatcherService.Interfaces.IFilesDuplex">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex"
binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true "/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
I finally got it.
Had to make a few changes to my Client app.config and had to turn off security.
app.config(client):
<!-- WCF Client information-->
<system.serviceModel>
<bindings>
<wsDualHttpBinding>
<binding name="WSDualHttpBinding_IFilesDuplex">
<security mode="None"/>
</binding>
</wsDualHttpBinding>
</bindings>
<client>
<endpoint
address="http://localhost:8899/CloudManager/CommunicationChannel1"
binding="wsDualHttpBinding"
bindingConfiguration="WSDualHttpBinding_IFilesDuplex"
contract="WCFCloudManagerFolderWatcherService.Interfaces.IFilesDuplex"
name="WSDualHttpBinding_IFilesDuplex">
<identity>
<userPrincipalName value="localhost"/>
</identity>
</endpoint>
</client>
and in the app.config for the serverside I also hat to set
<security mode="None"/>
Now the connection works.
I have this WCF service and I'm trying to apply authentication and authorization mechanism in it.
It's my first time to do this, what I have is this web.config serviceModel tag for the service:
<system.serviceModel>
<services>
<service name="RoleBasedServices.SecureServiceExternal" behaviorConfiguration="externalServiceBehavior">
<endpoint contract="AuthService.IService1" binding="wsHttpBinding" bindingConfiguration="wsHttpUsername" />
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="wsHttpUsername">
<security mode="Message">
<message clientCredentialType="UserName" negotiateServiceCredential="false" establishSecurityContext="false" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<!--To avoid disclosing metadata information, set the values below to false before deployment-->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!--To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information-->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
<behavior name="externalServiceBehavior">
<serviceAuthorization principalPermissionMode="UseAspNetRoles" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="MembershipProvider" />
<serviceCertificate findValue="RPKey" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
What I want to do is very simple, I don't know if I need all this tags I'm just trying.
What I want to do is from the client side to add reference for the service and first call the MyLogin:
AuthService.Service1Client s = new AuthService.Service1Client();
s.Login();
Then call the other restricted method and let it be GetData:
s.GetData()
At service side in Login method, and only for test purposes, I'm doing this:
public void Login()
{
Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("Bob"), new[] { "Admin" });
FormsAuthentication.SetAuthCookie("BobUserName", false);
}
An the restricted method will be:
[PrincipalPermission(SecurityAction.Demand, Role = "Admin")]
public void GetData()
{
return "Hello";
}
That all I have in service and client, what I'm missing?
Every time ,in debug, I check Thread.CurrentPrincipal in Login method I found Thread.CurrentPrincipal.Identity.IsAuthenticated equals true but even though when the client calls the GetData() method it's Access Denied.
PS: I'm using console application to do my tests does it make any difference ?
Thanks
Here is a very nice article that could possibly lead to a solution.
The general idea is that you have 2 object for the Principal.
HttpContext.Current.User and Thread.CurrentPrincipal. You are setting the Thread.CurrentPrincipal at the time HttpContext.Current.User is already instantiated and the role of it is left to default. You may want to try something like:
HttpContext.Current.User = new GenericPrincipal(new GenericIdentity("Bob"), new[] { "Admin" });
The reason calls to GetData() are denied is because WCF doesn't know anything about the Forms Authentication cookie that was set during Login().
It doesn't make a difference that you're a using console app. You could try the following approach.
Set the cookie in Login():
var cookie = FormsAuthentication.GetAuthCookie(username, true);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
HttpContext.Current.User = new GenericPrincipal(new FormsIdentity(ticket), null);
FormsAuthentication.SetAuthCookie(HttpContext.Current.User.Identity.Name, true);
Then in your console app:
public static void TestLoginAndGetData()
{
var sharedCookie = string.Empty;
using (var client = new YourClient())
using (new OperationContextScope(client.InnerChannel))
{
client.Login("username", "password");
// get the cookie from the response
HttpResponseMessageProperty response = (HttpResponseMessageProperty)
OperationContext.Current.IncomingMessageProperties[
HttpResponseMessageProperty.Name];
sharedCookie = response.Headers["Set-Cookie"];
// add it to the request
HttpRequestMessageProperty request = new HttpRequestMessageProperty();
request.Headers["Cookie"] = sharedCookie;
OperationContext.Current.OutgoingMessageProperties[
HttpRequestMessageProperty.Name] = request;
var result = client.GetData();
Console.WriteLine(result);
}
}
You might also consider changing the return type of GetData() to string.
I have webcam in a silverlight app. I capture the image and convert it to byte array and send to WCF service. Here is the image capture code:
MessageBox.Show("about to capture");
cs.CaptureImageAsync();
cs is the camera source and is configured correctly (as is the imagecapturecompleted event). On image capture completion, this code executes:
MessageBox.Show("Image Caputred");
var img = e.Result.ToImage();
var encoder = new PngEncoder();
Stream stream = img.ToStreamByExtension("png");
byte[] file = null;
if (stream.Length > 512000)
{
img = ExtendedImage.Resize(img, 240, new NearestNeighborResizer());
stream = img.ToStreamByExtension("png");
}
if (stream.Length < 512000)
{
BinaryReader binary = new BinaryReader(stream);
file = binary.ReadBytes((int)stream.Length);
MessageBox.Show("Stream read into file with length: " + file.Length);
}
else
{
MessageBox.Show("file size too large");
}
MessageBox.Show("Process done");
cs.Stop();
label1.Content = "and answer is : " + file!= null ? file.Length.ToString() : "ERROR";
ServiceReference1.Service1Client obj = new ServiceReference1.Service1Client();
ServiceReference1.ITEM i = new ServiceReference1.ITEM { Image = file };
obj.DoWorkCompleted += new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>(obj_DoWorkCompleted);
obj.DoWorkAsync(i);
This is my web.config in the asp.net project that configures the service:
<system.serviceModel>
<services>
<service name ="AttendanceSystem.IService1" behaviorConfiguration="BasicHttpBinding_IService1">
<endpoint address="" binding="basicHttpBinding" contract="AttendanceSystem.IService1"/>
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="BasicHttpBinding_IService1">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true"/>
And here is the reference to the service in the silverlight app
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1" maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:48886/Service1.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService1" contract="ServiceReference1.IService1"
name="default" />
</client>
</system.serviceModel>
Fiddler returns the following message on the call to my .svc service file:
HTTP/1.1 400 Bad Request
Server: ASP.NET Development Server/10.0.0.0
Date: Wed, 15 Jan 2014 09:17:26 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: private
Content-Length: 0
Connection: Close
So what is going on?
UPDATE: the service seems to be working without error when I pass a smaller byte array (for example BitConverter.GetBytes(123) but fails when the image is sent which is clearly within the max limits)
Try to configure the binding server-side (maxReceivedMessageSize and reader quotas)
<bindings>
<basicHttpBinding>
<binding maxReceivedMessageSize="2147483647">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647"/>
</binding>
</basicHttpBinding>
</bindings>
I have defined an own ServiceCredentials provider:
class PasswordServiceCredentials : ServiceCredentials
{
}
That provider generates a custom SecurityTokenManager in CreateSecurityTokenManager() method when I start my ServiceHost:
public override SecurityTokenManager CreateSecurityTokenManager()
{
if (this.UserNameAuthentication.UserNamePasswordValidationMode == UserNamePasswordValidationMode.Custom)
{
return new PasswordSecurityTokenManager(this);
}
return base.CreateSecurityTokenManager();
}
The PasswordSecurityTokenManager class:
class PasswordSecurityTokenManager : ServiceCredentialsSecurityTokenManager
{
}
The instance generates a custom SecurityTokenAuthenticator in CreateSecurityTokenAuthenticator() method:
public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
{
outOfBandTokenResolver = null;
return new PasswordSecurityTokenAuthenticator(this.ServiceCredentials
.UserNameAuthentication
.CustomUserNamePasswordValidator);
}
The generated instance is a custom CustomUserNameSecurityTokenAuthenticator.
The problem is that the overwritten ValidateUserNamePasswordCore() method is NOT CALLED at any time:
protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateUserNamePasswordCore(String userName, String password)
{
ReadOnlyCollection<IAuthorizationPolicy> currentPolicies = base.ValidateUserNamePasswordCore(userName, password);
List<IAuthorizationPolicy> newPolicies = new List<IAuthorizationPolicy>();
if (currentPolicies != null)
{
newPolicies.AddRange(currentPolicies.OfType<IAuthorizationPolicy>());
}
newPolicies.Add(new PasswordAuthorizationPolicy(userName, password));
return newPolicies.AsReadOnly();
}
In my custom IAuthorizationPolicy provider PasswordAuthorizationPolicy I want to set a custom pricipal for the EvaluationContext in Evaluate() method.
But if the upper method is not called, no additional IAuthorizationPolicy item can be defined.
What wrong or missing here?
I DO NOT use XML to configure my service, I do this 100% in C# code!
EDIT: The code ist based on the following blog article: http://www.neovolve.com/post/2008/04/07/wcf-security-getting-the-password-of-the-user.aspx
OK, I'm only asking because you never mentioned anything about your config file in your post, but are you setting your serviceCredentials type in your serviceBehaviors? Example:
<behaviors>
<serviceBehaviors>
<behavior name="YourCustomBehavior">
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials type="Your.Namespace.PasswordServiceCredentials, Your.Namespace">
<serviceCertificate findValue="localhost" x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom" />
</serviceCredentials>
<serviceAuthorization principalPermissionMode="Custom" />
</behavior>
</serviceBehaviors>
</behaviors>
And if you are, are you referencing "YourCustomBehavior" in your behaviorConfiguration on your service node? Example:
<services>
<service behaviorConfiguration="YourCustomBehavior"
name="Your.Service.Namespace.YourService">
<endpoint address="net.tcp://..."
binding="netTcpBinding" bindingConfiguration="netTcpBindingConfig"
contract="Your.Service.Interface.Namespace.IYourService" />
</service>
</services>
It might be as obvious as that.
(source: avivacommunityfund.org)