This may seem like a really easy question but I can't seem to figure it out at all.
I'm trying to create a new WCF service, and I'm new to having to secure them. I'm using a custom username/password for authentication. The problem [right now anyways] that I seem to be running into is that I can't figure out how to define the service to use the WSHttpBinding (on the service side, not the client side).
Am I missing something incredibly simple? Any pointers and/or recommendations would be greatly appreciated!
EDIT
Here's my code so far:
IAccountService
[ServiceContract]
public interface IAccountService
{
[OperationContract]
bool IsCardValid(string cardNumber);
[OperationContract]
bool IsAccountActive(string cardNumber);
[OperationContract]
int GetPointBalance(string cardNumber);
}
Service web.config
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false before deployment -->
<serviceMetadata httpGetEnabled="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"/>
<StructureMapServiceBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="StructureMapServiceBehavior" type="Marcus.Loyalty.WebServices.Setup.StructureMapServiceBehavior, Marcus.Loyalty.WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
<services>
<service name="Marcus.Loyalty.WebServices.Account.IAccountService">
<endpoint address=""
binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_Config"
contract="Marcus.Loyalty.WebServices.Account.IAccountService"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_Config"/>
</wsHttpBinding>
</bindings>
</system.serviceModel>
Testing app (console app)
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Please enter card number");
var number = Console.ReadLine();
var endPoint = new EndpointAddress("http://localhost:59492/Account/AccountService.svc");
var binding = new WSHttpBinding(SecurityMode.Message);
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
var cf = new ChannelFactory<IAccountService>(binding, endPoint);
cf.Credentials.UserName.UserName = "testuser";
cf.Credentials.UserName.Password = "Password1!";
var service = cf.CreateChannel();
var balance = service.IsAccountActive(number);
Console.WriteLine("\nBALANCE: {0:#,#}", balance);
Console.Write("\n\nPress Enter to continue");
Console.Read();
}
}
Testing app app.config
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="BasicHttpBinding_IAccountService" />
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:59492/Account/AccountService.svc"
binding="wsHttpBinding" bindingConfiguration="BasicHttpBinding_IAccountService"
contract="ServiceReference1.IAccountService" name="BasicHttpBinding_IAccountService" />
</client>
</system.serviceModel>
</configuration>
You need to define the abc (address, binding, contract) configuration into de web.config file (you can also do it programmatically. the b part, the binding, you can specify the wsHttpBinding
<system.serviceModel>
<services>
<service name = "MyNamespace.MyService">
<endpoint
address = "http://localhost:8000/MyService"
binding = "wsHttpBinding"
contract = "MyNamespace.IMyContract" />
</service>
</services>
</system.serviceModel>
If you wish to enable security in a proper way, there is a lot of literature and options. You can use certificates, windows based, tokens, ... passing a username & password like a parameter could not be the best way to do it.
There is an extensive sample on MSDN (How to: Specify a Service Binding in code) - but basically, you need to have:
your service contract (IMyService)
an implementation of that service (MyService)
a code where you create your ServiceHost to host your service
You got all of that? Great!
In that case, just do something like this:
// Specify a base address for the service
string baseAddress = "http://YourServer/MyService";
// Create the binding to be used by the service.
WsHttpBinding binding1 = new WsHttpBinding();
using(ServiceHost host = new ServiceHost(typeof(MyService)))
{
host.AddServiceEndpoint(typeof(IMyService), binding1, baseAddress);
host.Open();
Console.ReadLine();
}
and now you should have your service host up and running, on your chosen base address and with the wsHttpBinding defined in code.
Related
I have created a test WCF service WcfSampleLib with contract as IWcfSampleLib and ny service class is clsWcfSampleLib.
namespace WcfSampleLib
{
public class clsWcfSampleLib:IWcfSampleLib
{
public string getMsg(string name)
{
return " HI " + name;
}
}
[ServiceContract]
public interface IWcfSampleLib
{
[OperationContract]
string getMsg(string name);
}
}
Now I have added a window form application to host my WCF. I have created a App.config as
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="Beh">
<serviceMetadata httpGetEnabled="true" httpGetUrl="http://{server}:9097"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name ="WcfSampleLib.clsWcfSampleLib" behaviorConfiguration="Beh">
<endpoint address="http://{server}:9096/SomeName" binding="basicHttpBinding" contract ="WcfSampleLib.IWcfSampleLib"/>
<endpoint address="net.tcp://{server}:9095/SomeName1" binding="netTcpBinding" contract ="WcfSampleLib.IWcfSampleLib"/>
</service>
</services>
</system.serviceModel>
</configuration>
I have two endpoints to use WCF from two different clients with different end point.
I have added following lines of code in Form1_Load event of Host Application
host = new ServiceHost(typeof(WcfSampleLib.clsWcfSampleLib));
host.Open();
MessageBox.Show("started");
Now I can add reference of service with http://{server}:9097 only. Is there any way so that I can use both endpoints with different URL means net.tcp://{server}:9095/SomeName1 and http://{server}:9096/SomeName
Long story short:
My WCF clients should be able to provide both username and certificate to a service hosted in IIS, where I should use that information to validate requests using a custom policies.
Complete story:
I have the need to authenticate some WCF clients to verify if they can execute operations.
We have two kinds of clients: WPF applications and a web application. We would like to do the following:
The web application uses a certificate trusted by the service so that it is recognized as a special user with all permissions (the web application already verifies permissions by itself and we wouldn't like to touch it by now)
The WPF clients authenticate themselves with username/password provided by the user
In the implementation of the operations, I would like to verify if the certificate was provided (then I recognize the "super user"), otherwise fallback to username/password authentication.
Services are hosted in IIS 7 and we need to use NetTcpBinding.
I was able to implement the username validation, but the problem is that the AuthorizationContext inspected by the service contains only identity information, and not the certificate.
The following code is used on the client side to initialize the creation of channels (from a spike I'm using to test the solution):
var factory = new ChannelFactory<T>(this.Binding, address);
var defaultCredentials = factory.Endpoint.Behaviors.Find<ClientCredentials>();
factory.Endpoint.Behaviors.Remove(defaultCredentials);
var loginCredentials = new ClientCredentials();
loginCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.None;
loginCredentials.UserName.UserName = username;
loginCredentials.UserName.Password = password;
if (useCertificate)
{
loginCredentials.SetCertificate();
}
factory.Endpoint.Behaviors.Add(loginCredentials);
return factory.CreateChannel();
With the SetCertificate extension being implemented like this:
public static void SetCertificate(this ClientCredentials loginCredentials)
{
loginCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SecureWcfClient");
}
This is the configuration of the web application hosting the services:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="SecureBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="Test"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<clientCertificate>
<authentication certificateValidationMode="Custom" customCertificateValidatorType="AuthenticationProtectedService.Security.CertificateValidator, AuthenticationProtectedService.Security"/>
</clientCertificate>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="AuthenticationProtectedService.Security.UserNamePassValidator, AuthenticationProtectedService.Security" />
</serviceCredentials>
<serviceAuthorization serviceAuthorizationManagerType="AuthenticationProtectedService.Security.CertificateAuthorizationManager, AuthenticationProtectedService.Security"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding>
<security mode="None"/>
</binding>
<binding name="SecureNetTcp">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<service
name="AuthenticationProtectedService.Services.OneWayServiceB"
behaviorConfiguration="SecureBehavior">
<endpoint
address=""
binding="wsHttpBinding"
contract="AuthenticationProtectedService.ServiceModel.IOneWayServiceB">
</endpoint>
</service>
<service
name="AuthenticationProtectedService.Services.DuplexServiceB" behaviorConfiguration="SecureBehavior">
<endpoint
address=""
binding="netTcpBinding"
bindingConfiguration="SecureNetTcp"
contract="AuthenticationProtectedService.ServiceModel.IDuplexServiceB">
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
Finally, this is the implementation of the custom authorization manager (I also tried with a custom certificate validator but the function was never run)
public class CertificateAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
if (!base.CheckAccessCore(operationContext))
{
return false;
}
string thumbprint = GetCertificateThumbprint(operationContext);
// I'd need to verify the thumbprint, but it is always null
return true;
}
private string GetCertificateThumbprint(OperationContext operationContext)
{
foreach (var claimSet in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
foreach (Claim claim in claimSet.FindClaims(ClaimTypes.Thumbprint, Rights.Identity))
{
string tb = BitConverter.ToString((byte[])claim.Resource);
tb = tb.Replace("-", "");
return tb;
}
}
return null;
}
}
I think that the problem could be in the clientCredentialType property of the nettcpbinding.Security.Message node on the service configuration, but I don't see the option to use both Certificate and Username withing the Message security.
Any help appreciated, thanks
Remark: a specific goal of the project is to have very low level impact on server setup and in general in the system, so also SSL should be avoided if possible.
try out this link http://msdn.microsoft.com/en-us/library/ms733099.aspx ...it might resolve your issue where in you can have different binding configuration for same binding type and associate the same to different endpoints as per your need.
I am building a system with 2 WCF Services. Both are IIS Hosted. At the moment they both reside in a single VS2010 website app, running on my local IIS7 (Windows 7) using the Derfault Website. I have enabled net.tcp on both.
Service1
accepts HTTP posts using webHttpBinding
wraps the data in a serializable composite object
sends the composite object to Service2 (we hope) using netMsmqBinding
Service2
receives said message and does something with it
Service 1 works as expected, however instead of placing the message on the configured Private Queue, our code is creating a new Queue under "Outgoing Queues" with the handle
DIRECT=TCP:127.0.0.1\private$\Service2/Service2.svc
note the forward slash
Of course Service2 never sees the message - this is the first time I have attempted this structure so I am not certain that Service2 misses the message because of its location, but based on what I have read it would seem so - I have not come across anything mentioning this Queue-creation behaviour.
Questions:
Am I doing this correctly (is there something wrong in the structure, web.config or code)?
When done properly in VS Debug, should Service1's
proxy.ProcessForm(formMessage);
hit breakpoints in my Service2 code, or is there another way to hande Service2 debug (ala windows services for example)?
Service1 Web.Config
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="webHttpFormBinding" crossDomainScriptAccessEnabled="true"/>
</webHttpBinding>
<netMsmqBinding>
<binding name="MsmqFormMessageBindingClient" exactlyOnce="false" useActiveDirectory="false" >
<security mode="None">
<message clientCredentialType="None"/>
<transport msmqAuthenticationMode="None" msmqProtectionLevel="None" />
</security>
</binding>
</netMsmqBinding>
</bindings>
<client>
<endpoint
name="HttpServiceWebEndpoint"
address=""
binding="webHttpBinding"
bindingConfiguration="webHttpFormBinding"
contract="Service1.HttpService.IHttpServiceWeb" />
<endpoint name="MsmqFormMessageBindingClient"
address="net.msmq://127.0.0.1/private/Service2/Service2.svc"
binding="netMsmqBinding"
contract="MyInfrastructure.IService2" />
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="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"/>
<!--
<serviceAuthenticationManager />
-->
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
On Receipt of an HTTP Post Service1 executes the following:
StreamReader sr = new StreamReader(formData);
string str = sr.ReadToEnd();
var t = HttpUtility.ParseQueryString(str);
Hashtable nvc = new Hashtable();
foreach (string n in t)
{
nvc.Add(n, (string)t[n]);
}
WcfFormMessage formMessage = new WcfFormMessage(nvc);
////create the Service binding
NetMsmqBinding msmq = new NetMsmqBinding("MsmqFormMessageBindingClient");
msmq.Security.Mode = (NetMsmqSecurityMode) MsmqAuthenticationMode.None;
EndpointAddress address = new EndpointAddress("net.msmq://127.0.0.1/private/Service2/Service2.svc");
ChannelFactory<IService2> factory = new ChannelFactory<IFormService>(msmq,address);
IService2 proxy = factory.CreateChannel();
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
proxy.ProcessForm(formMessage);
//do any 'sent to queue logging/updates here
}
I am ready to bet that your problem is related to 127.0.0.1 in your config. Type the machine name in there, even if it is local.
I have a big problem. I am trying to create a web service that will work with a distributed transaction.
All the code below is on the server side of the web service(the web service that is called from a client).
I wrote this in my interface:
[ServiceContract]
public interface IClientOperations
{
[OperationContract]
[ServiceKnownType(typeof(TriggerExecInput))]
[ServiceKnownType(typeof(TriggerExecOutput))]
[TransactionFlow(TransactionFlowOption.Mandatory)]
TriggerExecOutput TriggeredProfileDataUpdate(TriggerExecInput triggerInputData, bool isST3StatusActive);
And this in the web.config file:
<services>
<service name="ClientOperationsService" behaviorConfiguration="ServiceBehavior">
<endpoint address="" binding="wsHttpBinding"
bindingConfiguration="wsHttpBinding_Common" contract="SL.STAdmin.Applications.WebAPI.IClientOperations"/>
<endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBinding_Common" transactionFlow="true">
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="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>
</serviceBehaviors>
</behaviors>
If I right click the .svc file and click on "View in browser" I get the following error
Exception Details: System.InvalidOperationException: At least one operation on the 'ClientOperations' contract is configured with the TransactionFlowAttribute attribute set to Mandatory but the channel's binding 'BasicHttpBinding' is not configured with a TransactionFlowBindingElement. The TransactionFlowAttribute attribute set to Mandatory cannot be used without a TransactionFlowBindingElement.
I have other .svc files that don't use transactions.
They all work well.
I don't understand why it still tries to use the BasicHttpTransaction when I instruct it to use the other binding type.
DOes anyone have any idea what I am doing wrong?
Thank you in advance.
Add this inside your <system.serviceModel> element of your web.config:
<protocolMapping>
<add scheme="http" binding="wsHttpBinding" bindingConfiguration="wsHttpBinding_Common"/>
</protocolMapping>
You need to do a few things to get the transaction working.
Add the transactionflow to your operation
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
void TransactionSupported(int id, string name);
After that you add an operationbehavior to your implementation
[OperationBehavior(TransactionScopeRequired = true)]
public void TransactionSupported(int id, string name)
{
...
}
In your config file you need to add the transaction flow to your host binding
<system.serviceModel>
...
<bindings>
<netNamedPipeBinding> --> Your binding (don't use basicHttpBinding)
<binding transactionFlow="true"/>
</netNamedPipeBinding>
</bindings>
</system.serviceModel>
And last but not least you need to set the transactionflow of your client to get it working. In my example I do this in my code in my unit test, I think you can also do this in your configuration of your client, in your config file.
var factory = new ChannelFactory<IService>(callback,
new NetNamedPipeBinding() { TransactionFlow = true },
new EndpointAddress("net.pipe://localhost/ping"));
I came across a page on MSDN explaining transaction in WCF Services here. I tweaked the binding settings and used netTcpBinding. Here is the serviceModel section of my app.config file:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="netTcpBindingConfiguration1" transactionFlow="true">
<security mode="Message" />
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="OrderingService.OrderService">
<clear />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"
listenUriMode="Explicit">
</endpoint>
<endpoint address="net.tcp://localhost:8880/OrderingService"
binding="netTcpBinding" bindingConfiguration="netTcpBindingConfiguration1"
contract="OrderingService.IOrderService" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8888/OrderingService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
I created a windows application as the client of the service. I used netstat command to see the TCP connections between the client and the service (hosted in a console application). I realized for each operation (which was a button click in my client app that places a new order by invoking the methods of the service's proxy class), a new connection is created and all previous connections still remain ESTABLISHED. Obviously, this is not an ideal condition. I wondered what I did wrong and what setting or configuration would work out this problem by reducing the number of connections to only one. By the way, the service class that implements the service interface has InstanceContextMode set to PerSession. Here are the contract interface and the service class:
[ServiceContract(SessionMode=SessionMode.Required)]
public interface IOrderService
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.NotAllowed)]
List<Customer> GetCustomers();
[OperationContract]
[TransactionFlow(TransactionFlowOption.NotAllowed)]
List<Product> GetProducts();
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string PlaceOrder(Order order);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string AdjustInventory(int productId, int quantity);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string AdjustBalance(int customerId, decimal amount);
}
[ServiceBehavior(TransactionIsolationLevel = IsolationLevel.Serializable,
TransactionTimeout = "00:00:20",
InstanceContextMode = InstanceContextMode.PerSession,
TransactionAutoCompleteOnSessionClose = true)]
public class OrderService : IOrderService
{...}
Here is the code the uses the proxy class in the client app:
using (TransactionScope scope = new TransactionScope())
{
try
{
proxy = new OrderServiceClient("NetTcpBinding_IOrderService");
result = proxy.PlaceOrder(order);
MessageBox.Show(result);
result = proxy.AdjustInventory(product.ProductId, quantity);
MessageBox.Show(result);
result = proxy.AdjustBalance(customer.CustomerId, product.Price * quantity);
MessageBox.Show(result);
proxy.Close();
scope.Complete();
}
catch (Exception exc)
{
MessageBox.Show("Error occurred: " + exc.Message);
}
}
With regards to the TCP connection remaining ESTABLISHED - are you calling .Close() on your instance of the client when you are finished with it?
If you want to use a single connection you should change the instance context mode to 'Single' and reuse the connection you establish in the client to process all your service calls. This suits an architecture where you want to maintain state within your service.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Service : IService
{
}
I found this link very helpful when I was learning about context modes in WCF: CodeProject link
As you are currently using PerSession context mode you should be able to limit it to a single connection by adding a setting for maxConcurrentSessions in your behaviors section. You can do it like this:
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
<serviceThrottling maxConcurrentSessions="1" />
</behavior>
</serviceBehaviors>
This would only be a good idea if you have a single client.