This question is a sort of follow up to this one: How to create a .NET client for a wso2 Secure Token Service
Briefly, I am trying to implement a client for a web service in a federated security scenario. My client should invoke a method of a given web service authenticating itself with a security token provided by another web service (both services are implemented with wso2 platform).
As I stated in the answer to the above question, with the proper binding configuration, the client is able to receive the requested token. The following is my binding configuration:
<wsFederationHttpBinding>
<binding name="fs">
<security mode="TransportWithMessageCredential">
<message issuedKeyType="SymmetricKey" issuedTokenType ="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0">
<issuer address =<!-- STS URL HERE--> binding ="customBinding" bindingConfiguration ="StsBinding"/>
<claimTypeRequirements>
<add claimType="http://wso2.org/claims/userid" />
</claimTypeRequirements>
</message>
</security>
</binding>
</wsFederationHttpBinding>
...
<customBinding>
<binding name="StsBinding">
<textMessageEncoding messageVersion="Soap12WSAddressing10"/>
<useManagedPresentation/>
<security authenticationMode="UserNameOverTransport" includeTimestamp ="true" keyEntropyMode ="ServerEntropy" securityHeaderLayout ="Lax"
messageSecurityVersion="WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10" >
</security>
<httpsTransport authenticationScheme ="Basic"/>
</binding>
</customBinding>
However, when my client process the recieved token it fails with a SecurityNegotiationException stating that the "urn:IssueTokenResponse" action is wrong. What does this exception means? What should be the correct action?
I don't have access to any details of both services so I need to know if I can do something on client side only.
I have tried to follow the advice contained in this forum post https://social.msdn.microsoft.com/Forums/vstudio/en-US/6c838f7e-f72f-4fdd-827d-b29c61522aa0/wrong-action-httpdocsoasisopenorgwssxwstrust200512rstrissue?forum=wcf but I don't think it applies to my case because there isn't a single messageSecurityVersion value which seems to work
I finally find a working solution, at least for the "wrong action" error.
Digging through the WCF documentation I found a reference document describing how to set-up a Security Token Service (MSDN address here)
The most intresting part of the document is this small phrase that seems to indicate the expected action for a response sent by a STS:
In addition, it defines the associated Action Uniform Resource
Identifiers (URIs). The action URI associated with the
RequestSecurityToken message is
http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue. The action URI
associated with the RequestSecurityTokenResponse message is
http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue.
After some more research on the extensbility mechanism provided by the WCF framework I found a promising reference about IClientMessageInspector that allows to customize client behavior when sending requests or when receiving replies.
The following is the simple code of the behavior:
Public Class ChangeReplyActionMessageInspector
Implements IClientMessageInspector
Public Sub AfterReceiveReply(ByRef reply As Message, correlationState As Object) Implements IClientMessageInspector.AfterReceiveReply
If reply.Headers.Action = "urn:IssueTokenResponse" Then
reply.Headers.Action = "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue"
End If
End Sub
Public Function BeforeSendRequest(ByRef request As Message, channel As ServiceModel.IClientChannel) As Object Implements IClientMessageInspector.BeforeSendRequest
Return Nothing
End Function
End Class
To attach this custom behavior to the client object responsible to talk to the Security Token Service I need a IEndpointBehavior like this one:
Public Class ChangeReplyActionEndpointBehavior
Implements IEndpointBehavior
Public Sub AddBindingParameters(endpoint As ServiceEndpoint, bindingParameters As BindingParameterCollection) Implements IEndpointBehavior.AddBindingParameters
End Sub
Public Sub ApplyClientBehavior(endpoint As ServiceEndpoint, clientRuntime As ClientRuntime) Implements IEndpointBehavior.ApplyClientBehavior
clientRuntime.ClientMessageInspectors.Add(New ChangeReplyActionMessageInspector)
End Sub
Public Sub ApplyDispatchBehavior(endpoint As ServiceEndpoint, endpointDispatcher As EndpointDispatcher) Implements IEndpointBehavior.ApplyDispatchBehavior
End Sub
Public Sub Validate(endpoint As ServiceEndpoint) Implements IEndpointBehavior.Validate
End Sub
End Class
That it is programmatically attached to the client with the following code:
Dim endpointBehaviorCollection As New System.Collections.Generic.KeyedByTypeCollection(Of IEndpointBehavior)
endpointBehaviorCollection.Add(New ChangeReplyActionEndpointBehavior)
client.ClientCredentials.IssuedToken.IssuerChannelBehaviors.Add(New Uri("STS URL HERE"), endpointBehaviorCollection)
In this way the issued security token is sent back to the target service with the final request. I am still getting errors for the final request that however needs further investigation.
Related
How do I change my wcf service to be able to accept mustunderstand = 1?
This is a scenario where I have to change the service to be able to accept a request from the client. The client sends mustunderstand =1 in the header.
The service is configured to use basichttpBinding
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
<transport clientCredentialType="None"></transport>
</security>
Using soap UI I insert the following username token into the header
<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-2684C13EA73A35131015516775308851">
<wsse:Username>username</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
I can reproduce the issue on soap UI when I insert this token in the wcf service request. This is the error
<FaultMsgRec>
<ErrCode>100</ErrCode>
<ErrCat>Error</ErrCat>
<ErrDesc>An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.--> The header 'Security' from the namespace 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' was not understood by the recipient of this message, causing the message to not be processed. This error typically indicates that the sender of this message has enabled a communication protocol that the receiver cannot process. Please ensure that the configuration of the client's binding is consistent with the service's binding. </ErrDesc>
</FaultMsgRec>
Since I have control over the wcf service I can go and add ValidateMustUnderstand = false in the service behavior.
Just like it is explained in the link
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.description.mustunderstandbehavior.validatemustunderstand?view=netframework-4.7.2
Once I add this to the service behavior the error disappears.
But I don't want to turn off validation on the header especially if its a username, password. What should I do to allow mustunderstand=1? Am I missing something that the service doesn't automatically process mustunderstand=1 by default. I know there is code to be written on the client in order to sent a 0 in the header.
I am using message contracts in my wcf service not data contract. I understand that for certain properties I can go and add attributes like this link
https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.messageheaderattribute.mustunderstand?view=netframework-4.7.2. But I am not adding to any properties. I am just adding it to the first linke in soapenv:mustunderstand=1
Please help!.
Thank you
Not sure whether this could solve your problem. But you could try to add your header in web.config.
<endpoint address="http://ws-wuxipc-5077:4000/calculator" binding="basicHttpBinding"
contract="ServiceInterface.ICalculatorService" name="cal">
<headers>
<Security xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" >
<wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>
</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">monMonDePasse</wsse:Password>
<wsse:Nonce>sdsdsdlojhfdsdM5Nw==</wsse:Nonce>
<wsu:Created>2019-01-21T6:17:34Z</wsu:Created>
</wsse:UsernameToken>
</Security>
</headers>
</endpoint>
Or you could add header using code.
using (ChannelFactory<ICalculatorService> ChannelFactory = new ChannelFactory<ICalculatorService>("cal"))
{
ICalculatorService employeeService = ChannelFactory.CreateChannel();
using (OperationContextScope scope = new OperationContextScope((IContextChannel)employeeService))
{
System.Xml.XmlDocument document = new XmlDocument();
XmlElement element = document.CreateElement("wsse", "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
XmlElement newChild = null;
newChild = document.CreateElement("wsse", "Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
newChild.InnerText = "finance";
element.AppendChild(newChild);
newChild = document.CreateElement("wsse", "password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
newChild.SetAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
newChild.InnerText = "387";
element.AppendChild(newChild);
MessageHeader messageHeader = MessageHeader.CreateHeader("security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", element, false); // here is mustunderstood is set to false
OperationContext.Current.OutgoingMessageHeaders.Add(messageHeader);
}
Console.Read();
}
I need help converting the following netTcpBinding to an equivalent CustomBinding:
<bindings>
<netTcpBinding>
<binding name="secureNetTcp" openTimeout="00:00:25" closeTimeout="00:00:27" receiveTimeout="00:10:10" sendTimeout="00:01:00"
listenBacklog="50" maxBufferPoolSize="2097152" maxBufferSize="2097152" maxConnections="50" maxReceivedMessageSize="2097152">
<readerQuotas maxArrayLength="2097152" />
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="true" />
<security mode="TransportWithMessageCredential">
<message algorithmSuite="Basic256Sha256" />
</security>
</binding>
</netTcpBinding>
</bindings>
I'm mostly struggeling with the Security part of the custom binding, because I can't fathom all the different settings. And everything seems to be named differently as well (compared to netTcpBinding parameters).
In case it's necessary I'll provide the following information as well:
The service endpoint has a certificate attached to it via serviceBehavior.
In my code I provide a username/password when creating the proxy (service behavior has <userNameAuthentication userNamePasswordValidationMode="Windows" /> under serviceCredentials; For the netTcpBinding the WCF configuration editor shows ClientCredentialType=Windows, which I guess is the default value).
Update:
I have found a potential solution for my main problem - increasing ChannelInitilizationTimeout - without having to create a CustomBinding. I'll share this, incase someone stumbels upon this thread while googeling...
What I did was create a custom class that inherits from NetTcpBinding and in it's constructor used reflection to set the ChannelInitilizationTimeout property. Thus maintaining full compatibility with NetTcpBinding.
Here is the code for my custom class:
public class MyNetTcpBinding : NetTcpBinding
{
public MyNetTcpBinding()
{
var fi = typeof(NetTcpBinding).GetField("transport", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var val = (System.ServiceModel.Channels.TcpTransportBindingElement)fi.GetValue(this);
val.ChannelInitializationTimeout = TimeSpan.FromSeconds(10);
}
}
public class MyBindingElement : NetTcpBindingElement
{
protected override Type BindingElementType
{
get { return typeof(MyNetTcpBinding); }
}
}
public class MyBindingElementCollection : StandardBindingCollectionElement<MyNetTcpBinding, MyBindingElement>
{
}
After compiling this class (I created a seperate DLL project for this class), I used WCF configuration editor (under left pane "Configuration" -> Advanced -> Extensions -> binding extensions -> new -> give a name, eg. "MyNetTcp" and point to the dll file) to add my class as an extension to bindings.
Afterwards in WCF app.config just replace netTcpBinding with MyNetTcp (there are three references in total; one in <service><endpoint binding="netTcpBinding"></endpoint></service>; the other two are xml tags under <bindings><netTcpBinding></netTcpBinding></bindings>).
I will leave this question open in case someone wants to give a proper answer to the original question...
You can pass in the netTcpBinding into a custom binding and do the following.
There is no guarantee that reflection will work across versions.
http://blogs.msdn.com/b/sajay/archive/2010/01/29/how-to-create-a-custom-binding-from-a-standardbinding.aspx
I'm trying to write a WCF 4.0 service that will receive SOAP alerts from TFS 2010. Here is my service contract:
[ServiceContract(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03")]
public interface IService1
{
[OperationContract(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify")]
[XmlSerializerFormat(Style = OperationFormatStyle.Document)]
void Notify(string eventXml, string tfsIdentityXml);
}
I am using this binding configuration for my service endpoint:
<bindings>
<wsHttpBinding>
<binding name="noSecurity">
<security mode="None"/>
</binding>
</wsHttpBinding >
</bindings>
Having registered a TFS alert using BISSUBSCRIBE.EXE and pointed it to my service, every time it is triggered my service is not invoked and instead I see the following in the TfsJobAgent log:
Notification not delivered.
Notification: WorkItemChangedEvent (DeliveryType: Soap; Address: http://192.168.10.10/TrafficLight/Service1.svc)
Exception: Microsoft.TeamFoundation.TeamFoundationServiceUnavailableException: Team Foundation services are not available from server http://192.168.10.10/TrafficLight/Service1.svc. Technical information (for administrator):
HTTP code 404: Not Found ---> System.Net.WebException: The remote server returned an error: (404) Not Found.
at System.Net.HttpWebRequest.GetResponse()
at Microsoft.TeamFoundation.Client.TeamFoundationClientProxyBase.AsyncWebRequest.ExecRequest(Object obj)
--- End of inner exception stack trace ---
at Microsoft.TeamFoundation.Client.TeamFoundationClientProxyBase.ProcessHttpResponse(HttpWebResponse response, Stream responseStream, WebException webException, XmlReader& xmlResponseReader)
at Microsoft.TeamFoundation.Client.TeamFoundationClientProxyBase.ExecWebServiceRequest(HttpWebRequest request, XmlWriter requestXml, String methodName, HttpWebResponse& response)
at Microsoft.TeamFoundation.JobService.Extensions.Core.TeamFoundationNotificationClient.Notify(String eventXml, String tfsIdentityXml, Subscription subscription)
at Microsoft.TeamFoundation.JobService.Extensions.Core.NotificationJobExtension.SendSoapNotification(TeamFoundationRequestContext requestContext, TeamFoundationNotification notification, TeamFoundationIdentityService identityService)
(This is taken from the [Tfs_Configuration].[dbo].[tbl_JobHistory] table of my TFS 2010 installation)
Oddly enough, when I try my service's URL in internet explorer on the same machine where the TfsJobAgent is running, I receive the standard "You have created a service." web page auto-generated by WCF and not a 404 error.
At last, my question: Why is the TFS Job Agent receiving a 404 error from my service which seems to be properly configured? How can I resolve this issue?
Update: I've tried rewriting my service as an ASMX web service and it's working well. Below is the implementation. I still want to learn how to achieve the same using WCF 4.0 so any help would be greatly appreciated.
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class WebService1 : System.Web.Services.WebService
{
[SoapDocumentMethod("http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", RequestNamespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03")]
[WebMethod]
public void Notify(string eventXml, string tfsIdentityXml)
{
// log incoming event...
}
I have created this succesfully - http://www.ewaldhofman.nl/post/2010/08/02/How-to-use-WCF-to-subscribe-to-the-TFS-2010-Event-Service-rolling-up-hours.aspx - and it seems that you are using the same steps.
Did you also specify the wsHttpBinding for the endpoint?
I am developing WCF services with basicHttpBinding, these services should be accessible using .net 1.1 & .net 2.0, for this purpose I am using basicHttpBinding. In old ASMX web services I assed one Soap Header (AuthHeader) to authenticate the user every request.How Can I authenticate in WCF using basicHttpBinding? Any sample Or tutorial will helpfull.
nRk
You can use AuthHeader as you did before switching to WCF. Maybe it will be more convinient for you, cause the princples will remain the same.
The bad thing i see in this solution is a plain text password transfer. Anyway, it's just another option and you can encrypt/decrypt the password somehow.
In this case you should implement your own your IDispatchMessageInspector & IClientMessageInspector, like
[AttributeUsage(AttributeTargets.Class)]
public class CredentialsExtractorBehaviorAttribute : Attribute, IContractBehavior, IDispatchMessageInspector
{
#region IContractBehavior implementation.
public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint,
DispatchRuntime dispatchRuntime)
{
dispatchRuntime.MessageInspectors.Add(this);
}
... empty interface methods impl skipped ...
#endregion
#region IDispatchMessageInspector implementation.
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
int i = request.Headers.FindHeader("username", "sec");
if (-1 != i)
{
string username = request.Headers.GetHeader<string>("username", "sec");
... do smth ...
}
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
return;
}
#endregion
}
In a sample i placed to header only username, but you can implement your a class containing username and password and use it instead of string.
On the client:
internal class CredentialsInserter : IContractBehavior, IClientMessageInspector
{
private string m_username;
public CredentialsInserter(string username)
{
m_username = username;
}
#region IContractBehavior implementation.
... empty interface methods impl skipped ...
public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(this);
}
#endregion
#region IClientMessageInspector implementation.
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
MessageHeader<string> mh = new MessageHeader<string>(m_username);
request.Headers.Add(mh.GetUntypedHeader("username", "sec"));
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
return;
}
#endregion
}
Then you should place attribute CredentialsExtractorBehaviorAttribute on your service implementation class.
[CredentialsExtractorBehavior]
public class DummyService : IDummyService
{
... impl ...
}
And on the client side you should do the following:
using (DummyServiceClient c = new DummyServiceClient("TcpEndpoint"))
{
c.ChannelFactory.Endpoint.Contract.Behaviors.Add(
new CredentialsInserter("_username_"));
c.DummyMethod();
}
First of all - yes you can! It depends on whether you use Transport or Message binding - if you're internet-facing, you're more likely to use message-based security.
Unfortunately, for message-based security, basicHttpBinding only supports certificates which is a bit of a pain.
wsHttpBinding on the other hand would support username/password or other methods as well.
You'd configure wsHttpBinding with username/password client credentials over message-based security like this:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsUserName">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="yourservice">
<endpoint name="YourEndpoint"
address=""
binding="wsHttpBinding"
bindingConfiguration="wsUserName"
contract="IYourService" />
</service>
</services>
</system.serviceModel>
The section under <bindings> defines a binding configuration for wsHttpBinding that uses message-security with username/password client credentials.
The section under <service> defines a sample service that uses wsHttpBinding and that references that binding configuration that we just defined.
On the server side, you could now use the username/password that's being sent over the wire to validate your callers either in your Active Directory (everyone calling needs an AD account with you), or in the ASP.NET membership system database; or if you really really must, you could write your own authentication mechanism, too.
Find a lot of useful information on WCF security at Codeplex - excellent resource.
Check the scenarios here to try to match one to your situation. Each scenario is provided with a chceklist of items required to implement the solution.
I have a net tcp WCF service as follows
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class AVService : IAVService
{
static int _numberofInst = 0;
public AVService()
{
++_numberofInst;
Console.WriteLine("Number of instances "+_numberofInst);
}
~AVService()
{
--_numberofInst;
Console.WriteLine("Number of instances " + _numberofInst);
}
public void Foo(){}
}
When I create an object on the client side as follows
AVService client = new AVService();
client.Foo();
The constructor is called, but when I close the client application without calling close mehotd, the destructor does not been called? why? does this mean the service object still works on the server?
Yes - if you don't explicitly dispose of your client, the server will "hang around" for a while (since you specified PerSession mode).
It will eventually time out (specified by the InactivityTimeout setting on the binding configuration), and will be destroyed. But that could take some time (several minutes to several hours, depending on your settings).
<bindings>
<netTcpBinding>
<binding name="NetTcp_Reliable" receiveTimeout="00:20:00">
<reliableSession enabled="true" ordered="false"
inactivityTimeout="00:15:00" />
</binding>
</netTcpBinding>
</bindings>
Therefore, it is the best practice to always dispose of your client before closing the app.