2
votes

From a C# program I am trying to call a WebService written in Java. If I use SoapUi to call the WS I can see in fiddler that the call looks like this:

<soap:Envelope xmlns:ser="http://service.webservice.com" xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
       <soap:Header>
           <wsse:Security soap:mustUnderstand="true" 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-8684DEB94ABXXXXXXXXXX362973">
                   <wsse:Username>MyUserName</wsse:Username>
                   <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">MyPassword</wsse:Password>
                   <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">fjwe5h78k7vgheedRv21g==</wsse:Nonce>
                   <wsu:Created>2016-11-17T11:53:56.297Z</wsu:Created>
               </wsse:UsernameToken>
           </wsse:Security>
       </soap:Header>
       <soap:Body>
          <ser:getTrades>
                <ser:filter>
                <ser:fromValueDate>2015-03-23</ser:fromValueDate>
                <ser:toValueDate>2015-03-23</ser:toValueDate>
             </ser:filter>
          </ser:getTrades>
       </soap:Body>
</soap:Envelope>

The Problem is that VisualStudio creates the classes without the header options

// CODEGEN: The optional WSDL extension element 'Policy' from namespace 
'http://schemas.xmlsoap.org/ws/2004/09/policy' was not handled.

So how do I add the Soap header with the username and pw to make the call in C# ?

I also been looking in using WCF, and from the wsdl (here in part)

    .
    .
    .
    <wsdl:binding name="TradeRetrieverServiceSOAP12Binding" type="ns0:TradeRetrieverServicePortType">
    <wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" Id="UsernameTokenOverHTTPS">
    <wsp:ExactlyOne>
    <wsp:All>
    <sp:TransportBinding xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
    <wsp:Policy>
    <sp:TransportToken>
    <wsp:Policy>
    <sp:HttpsToken RequireClientCertificate="false"/>
    </wsp:Policy>
    </sp:TransportToken>
    <sp:AlgorithmSuite>
    <wsp:Policy>
    <sp:Basic256/>
    </wsp:Policy>
    </sp:AlgorithmSuite>
    <sp:Layout>
    <wsp:Policy>
    <sp:Lax/>
    </wsp:Policy>
    </sp:Layout>
    </wsp:Policy>
    </sp:TransportBinding>
    <sp:SignedSupportingTokens xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy">
    <wsp:Policy>
    <sp:UsernameToken sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient"/>
    </wsp:Policy>
    </sp:SignedSupportingTokens>
    </wsp:All>
    </wsp:ExactlyOne>
    </wsp:Policy>
    .
    .

it creates this bindings:

    <system.serviceModel>
        <bindings>
          <customBinding>
            <binding name="TradeRetrieverServiceSOAP12Binding">
              <security defaultAlgorithmSuite="Default" authenticationMode="UserNameOverTransport"
                requireDerivedKeys="true" securityHeaderLayout="Lax" includeTimestamp="false">
                <localClientSettings detectReplays="false" />
                <localServiceSettings detectReplays="false" />
              </security>
              <textMessageEncoding messageVersion="Soap12" />
              <httpsTransport />
            </binding>
          </customBinding>
        </bindings>
        <client>
          <endpoint address="http://Myservices/TradeRetrieverService"
            binding="customBinding" bindingConfiguration="TradeRetrieverServiceSOAP12Binding"
            contract="ServiceReference1.TradeRetrieverServicePortType" name="TradeRetrieverServiceSOAP12port_http" />
        </client>
    </system.serviceModel>

But this gives me this error:

    The provided URI scheme 'http' is invalid; expected 'https'.
    Parameter name: via

The url is a http and not https

So I am stock here. Any ideas?

EDIT

I now get data, well in fiddler, in code I get this error: 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.

1
Have you considered to buy a certifcate for the WS so that you can switch over to https? Thats not more than 20$/year..Marc
I have no control over the WS, and the develepers told me that there is no certificate or other security on the WS (part for the username and PW in the soap header )Toko

1 Answers

1
votes

Sadly, Microsoft decided to remove the support for WSSecurity / UsernameTokens in WCF. As i struggled with the same problem a few weeks ago, i digged a little into that issue, but couldn't find out why they did so. But there is hope, with a little bit effort.

First of all, if you do not want to use HTTPS, you need to specify that in the binding (You specified https). For example my binding (which is also using only HTTP) looks very simply like that:

<customBinding>
  <binding name="dummySoapBinding">
     <textMessageEncoding writeEncoding="UTF-8" messageVersion="Soap11" />
     <httpTransport />
  </binding>
</customBinding>

After that, to create the necessary WSS Security header in the request, i defined an custom behaviour which i applied to the endpoint. This behaviour attaches a MessageInspector to the ClientRuntime which sends the request. This message inspector alters the message before sending, and adds an Security Header. You can find some information about custom behaviours here. Information about message inspectors is available here.

My MessageInspector currently looks like this:

public string Username { get; set; }

public string Password { get; set; }

public PasswordDigestMessageInspector(string username, string password)
{
    Username = username;
    Password = password;
}

public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
{
    // do nothing
}

public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
{
    // generate token
    var usernameToken = new UsernameToken(this.Username, this.Password, PasswordOption.SendHashed);

    // save token as xml
    var securityToken = usernameToken.GetXml(new XmlDocument());
    var securityTokenText = securityToken.OuterXml;

    // remove vs data
    var limit = request.Headers.Count;
    for (var i = 0; i < limit; ++i)
    {
        if (!request.Headers[i].Name.Equals("VsDebuggerCausalityData")) continue;

        request.Headers.RemoveAt(i);
        break;
    }

    // set encoding type for nonce
    var nonceRegex = new Regex(@"<wsse:Nonce");
    securityTokenText = nonceRegex.Replace(securityTokenText,
        "<wsse:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\"");

    var newDoc = new XmlDocument();
    newDoc.LoadXml(securityTokenText);

    // create security header from message
    var securityHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", newDoc.DocumentElement, false);

    // add header to request message
    request.Headers.Add(securityHeader);

    // complete
    return Convert.DBNull;
}

The class UsernameToken i used here, has everything you need. It was used back in time, before WCF, but sadly has been removed. This class is defined in: Microsoft.Web.Services3.Security.Tokens. There should be nuget package for that. Alternatively you can manually install Microsoft's WebService Enhancements (WSE) 3.0 (here).

There is some additional logic which modifies some arguments, so that the WebService i call, will accept my request. It's very strict, and i have no option to modify it. You probably can ignore that.

Continuing, my behaviour which i add to the endpoint, looks like that:

public class PasswordDigestBehaviour : IEndpointBehavior
{
    public string Username { get; set; }

    public string Password { get; set; }

    public PasswordDigestBehaviour(string username, string password)
    {
        Username = username;
        Password = password;
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
        // do nothing
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new PasswordDigestMessageInspector(this.Username, this.Password));
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
    {
        // do nothing
    }

    public void Validate(ServiceEndpoint endpoint)
    {
        // do nothing
    }
}

Very simple. Finally, you can attach the behaviour to your WCF Client class:

client.Endpoint.Behaviors.Add(new PasswordDigestBehaviour("Testuser", "Testpassword"));

I hope it saves you some time, because i recently had much trouble with that topic.

Edit: I just saw that you're not sending the password hashed. You have to think of that in the constructor of "UsernameToken" as i am doing it hashed.