So I am working on an internal project where I am consuming a Java based SOAP Web Service from a WinForm application using a WCF Service Reference interface. (This is something new for me so I apologize if I not totally using the right terminology, etc.) I am sending my request over to the service and I am getting a response, although it has taken some hoop jumping to get there. The format of the response is what is causing me some problems in that it has excessive namespaces and the response size can get large.
I hope someone can point me to a simple solution / setting to reduce the excess.
A few things that I have no control over: The interface is via HTTP (not HTTPS) and security is handled via an App Key/ID mechanism using the a SOAP Security Header and client certificates. The server WSDL does not indicate that WSSE is required in the SOAP Header so the header is being overwritten by the security one prior to the send.
All items below are actual code structure and SOAP request/response format but have been changed to generic items. Also, the "response" is greatly reduced in size. With the right request parameters, a response is over 64k in length.
The SoapUI Request 1 example built with the WSDL load looks like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pric="http://xmlns.example.com/system/pricing" xmlns:ns="http://xmlns.example.com/system/pricing/shared/3.1.1">
<soapenv:Header/>
<soapenv:Body>
<pric: PricingRequest majorVersion="2" minorVersion="1">
<pric:version>?</pric:version>
<pric:timestamp>?</pric:timestamp>
<!--1 or more repetitions:-->
<pric:acctType>
<!--You have a CHOICE of the next 2 items at this level-->
<pric:customerNbr>?</pric: customerNbr >
<pric:accountNbr>?</pric:accountNbr>
</pric: acctType >
<!--1 to 3 repetitions:-->
<pric:system>?</pric: system >
<pric:source>?</pric:source>
<pric:searchItems>
<pric:purchaseDate>?</pric: purchaseDate >
<!--Zero or more repetitions:-->
<pric:service>
<ns:system>?</ns:system>
<ns:serviceType>?</ns:serviceType>
<!--Optional:-->
<ns:serviceDesc>?</ns:serviceDesc>
<!--Optional:-->
<ns:billingType>?</ns:billingType>
<!--Optional:-->
<ns:effectiveDate>?</ns:effectiveDate>
<!--Optional:-->
<ns:expirationDate>?</ns:expirationDate>
<!--Optional:-->
</pric:service>
</pric: searchItems >
</pric: PricingRequest >
</soapenv:Body>
</soapenv:Envelope>
The app.config Service Model - I added the maxReceivedMessageSize in order to handle the large responses:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="PricingServiceSoap11" maxReceivedMessageSize="256000" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://pricing-websvc.example.com:60000/PricingService"
binding="basicHttpBinding" bindingConfiguration="PricingServiceSoap11"
contract="pricingSvcRef.PricingService" name="PricingServiceSoap11" />
</client>
</system.serviceModel>
Code to consume service:
pricingSvcRef.PricingClient pricing = new pricingSvcRef.PricingClient();
var reqInfo = new InspectorBehavior();
pricing.Endpoint.Behaviors.Add(reqInfo);
pricingSvcRef.PricingRequest request = new pricingSvcRef.PricingRequest();
request.majorVersion = 2;
request.minorVersion = 1;
request.version = pricingSvcRef.PricingRequestVersion.Ver1;
request.timestamp = DateTime.Now;
request.acctType = new pricingSvcRef.PricingRequestAcctType[1];
request.acctType[0] = new pricingSvcRef.GetPricingRequestAcctype();
request.acctType[0].Item = txtAcctNo.Text.Trim();
request.system = "Pricing";
request.source = "MyApp";
List<pricingSvcRef.pricingService> svcList = new List<pricingSvcRef.pricingService>();
svcList.Add(
new pricingSvcRef.pricingService
{
system = txtSystem.Text.Trim(),
serviceType = Convert.ToInt32(txServiceType.Text.Trim(),
serviceDesc = txtService.Text.Trim(),
billingType = Convert.ToInt32(txBillingType.Text.Trim(),
effectiveDate = dateEffective.Value,
expirationDate = dateExpiration.Value
});
pricingSvcRef.PricingRequestSearchItems srchItems = new pricingSvcRef.PricingRequestSearchItems
{
purchaseDate = DateTime.Now;
service = svcList.ToArray(),
};
request.searchItems = srchItems;
pricingSvcRef.PricingResponse response = null;
try
{
response = eddSvcRef.GetPricing(request);
}
catch (Exception ex)
{
string msg = "Msg: " + ex.Message;
if ((ex.InnerException != null) && (ex.InnerException.Message.Trim().Length > 0))
msg = msg + "\r\n\r\nInner Msg: " + ex.InnerException.Message.Trim();
MessageBox.Show(msg);
}
SOAP Request that is generated:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<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">
<wsse:Username>v1:APP1234567:SfPLRHKdXWWqW7mdOdk+gTABVo+y4VZR7UqLHWibVD73jaI0IL9FT4PMgAVAxGsa83P/aon61GuS+IZnuVjRHR4hJgyMuCvtI07QtNaSdwEyw9Lw/Iewm+098fkMYK2pflV7w6hn0U2A9/wzuQS/vfWe53vhGSX1zfhuo6AygKA=:APP1234567</wsse:Username>
</wsse:UsernameToken>
</Security>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PricingRequest majorVersion="2" minorVersion="1" xmlns="http://xmlns.example.com/system/pricing">
<version>1.0.0</version>
<timestamp>2019-03-25T09:46:26.7337264-05:00</timestamp>
<acctType>
<accountNbr>123456789</accountNbr>
</acctType>
<system>Pricing</system>
<source>MyApp</source>
<searchItems>
<purchaseDate>2019-03-25</purchaseDate>
<service>
<system xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">MyPricing</system>
<serviceType xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">1000</serviceType>
<serviceDesc xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">Service Description</serviceDesc>
<billingType xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">1234</billingType>
<effectiveDate xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">2019-01-01</effectiveDate>
<expirationDate xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">2019-12-31</expirationDate>
</service>
</searchItems>
</PricingRequest>
</s:Body>
</s:Envelope>
SOAP Response in application:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
<PricingResponse xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1" xmlns:ns2="http://xmlns.example.com/system/pricing">
<pricingContainer xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">
<version xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">1.0.0</version>
<timestamp xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">2019-03-25T14:46:28.876Z</timestamp>
<source xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">MyApp</source>
<status xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">
<code xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">0000</code>
<description xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">SUCCESS</description>
</status>
<acctType xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">
<accountNbr xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">123456789</accountNbr>
</acctType>
<system xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">Pricing</system>
<serviceDetail xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">
<valid xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">Y</valid>
<system xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">MyPricing</system>
<serviceType xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">1000</serviceType>
<serviceDesc xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">Service Description</serviceDesc>
<billingType xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">1234</billingType>
<effectiveDate xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">2019-01-01</effectiveDate>
<expirationDate xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">2019-12-31</expirationDate>
<pricingRate xmlns="http://xmlns.example.com/system/pricing/shared/3.1.1">100</pricingRate>
</serviceDetail>
</pricingContainer>
</PricingResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
So even though namespaces are returning as part of the PricingResponse element, they are also on each child item below that.
UPDATE: I thought SoapUI responses were coming back w/o the excessive namespace items on each element but I have now determined using Wireshark and SoapUI Raw display that this not the case.
Is there anyway to request from the WCF Interface to the Java Web Service that the response uses namespace prefixes rather than having a namespace on almost every response element? Obviously I am going to keep digging at it but any help / guidance would be appreciated.
Thanks
Related
I have a SOAP web service that I need to call in a wide array applications, so I created a service DLL that targets both .net standard 2.0 and .net framework 4.6.1.
The DLL seems to work fine with on Desktop Framework applications, and on Desktop Core applications. However, the behavior on a Xamarin Android project is just slightly off of what the other two are.
A correctly sent message from the other two applications looks like this:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ZBAPI_GOODSMVT_CREATE xmlns="urn:sap-com:document:sap:rfc:functions">
<CODE xmlns="">
<GM_CODE>03</GM_CODE>
</CODE>
<HEADER xmlns="">
<PSTNG_DATE>2019-12-08</PSTNG_DATE>
<DOC_DATE>2019-12-08</DOC_DATE>
</HEADER>
<ITEM xmlns="">
... data here ...
</ITEM>
<RETURN xmlns="">
... data here ...
</RETURN>
</ZBAPI_GOODSMVT_CREATE>
</s:Body>
And the a successfully sent(but not processed) message from the android device is so:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ZBAPI_GOODSMVT_CREATE>
<CODE>
<GM_CODE>03</GM_CODE>
</CODE>
<HEADER>
<PSTNG_DATE>2019-12-08</PSTNG_DATE>
<DOC_DATE>2019-12-08</DOC_DATE>
</HEADER>
<ITEM>
... data here ...
</ITEM>
<RETURN>
... data here ...
</RETURN>
</ZBAPI_GOODSMVT_CREATE>
</s:Body>
So nearly identical, except every namespace inside the body element has been stripped out.
I've confirmed with SOAPUI that All the namespaces are needed, even the blank ones.
The DLL in question is, as stated, a .net standard 2.0, and the service was imported using the vendor provided wsdl file.
inside the DLL code I am calling the service like so:
private void RunRequest(ServiceData payload)
{
var binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
var address = new EndpointAddress("https:\\serviceaddress.com\service");
new ChannelFactory<ServiceChannel>(binding, address).Using(factory =>
{
factory.Credentials.UserName.UserName = "Uset";
factory.Credentials.UserName.Password = "Pass";
var proxy = factory.CreateChannel();
proxy.Open();
var context = new OperationContext((IClientChannel)proxy);
var prevOpContext = OperationContext.Current;
OperationContext.Current = context;
try
{
var results = proxy.ServiceChannel_CREATEAsync(payload).ConfigureAwait(false).GetAwaiter().GetResult().SerivceChannelResponse;
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(ServiceChannelResponse));
using StringWriter text = new StringWriter();
serializer.Serialize(text, results);
Message = text.ToString();
}
catch
{
throw;
}
});
how can I force the xmlns namespaces inclusion across all platforms?
I want to send a mock response from my service when the request matches on these characteristics:
URL matches with /BATConnectWS/services/CoverApplication
The HTTP method matches with POST
The XPath matcher must match an amount of 5000
Code setup:
var server = FluentMockServer.Start(new FluentMockServerSettings
{
Urls = new[] { "http://+:8099" },
StartAdminInterface = true,
Logger = new WireMockConsoleLogger()
});
server
.Given(Request.Create().WithPath("/*")).AtPriority(10)
.RespondWith(Response.Create()
.WithProxy("https://TheRealService.com/"));
server
.Given(Request.Create().WithPath("/BATConnectWS/services/CoverApplication").UsingPost()
.WithBody(new XPathMatcher(#"//applyForCreditLimit/application/RequestedAmount/text() = 5000")))
.AtPriority(1)
.RespondWith(Response.Create()
.WithHeader("Content-Type", "text/xml; charset=utf-8")
.WithCallback(req =>
{
return CoverApplicationResponseBuilder.CreateResponse(req);
}));
The request message:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" 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">
<soap:Header>
<wsa:Action>urn:applyForCreditLimit</wsa:Action>
<wsa:MessageID>urn:uuid:6da4a592-90a0-4623-8c71-1e685cbdac33</wsa:MessageID>
<wsa:ReplyTo>
<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:To>http://localhost:58070/BATConnectWS/services/CoverApplication</wsa:To>
<wsse:Security soap:mustUnderstand="1">
<wsu:Timestamp wsu:Id="Timestamp-6befddc7-4e4f-4a76-a203-49b729bd483a">
<wsu:Created>2019-10-15T08:22:37Z</wsu:Created>
<wsu:Expires>2019-10-15T08:27:37Z</wsu:Expires>
</wsu:Timestamp>
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-x-x-x-x-x">
<wsse:Username>xxx</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">xxx</wsse:Password>
<wsse:Nonce>xxx</wsse:Nonce>
<wsu:Created>2019-10-15T08:22:37Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<applyForCreditLimit xmlns="http://atradius.com/connect/_2007_08/">
<application>
<CustomerId xmlns="">1234</CustomerId>
<PolicyNr policyTypeIdentifier="NON_LEG" xmlns="">
<Id>5678</Id>
</PolicyNr>
<ExternalCoverId xmlns="">9101112</ExternalCoverId>
<CustomerReference xmlns="">areference</CustomerReference>
<Buyer registeredOffice="SYMPH" xmlns="">
<id xmlns="http://atradius.com/organisation/_2007_08/type/">13141516</id>
<countryTypeIdentifier xmlns="http://atradius.com/organisation/_2007_08/type/">AUT</countryTypeIdentifier>
</Buyer>
<RequestedAmount xmlns="">5000</RequestedAmount>
<CurrencyCode xmlns="">EUR</CurrencyCode>
</application>
</applyForCreditLimit>
</soap:Body>
</soap:Envelope>
Without the XPathMatcher the mocked response will be sent.
With the XPatchMatcher the real service will be called (pass through), because there was no WithBody match on the content.
What should the XPath query be like to match on the amount of 5000, in the RequestedAmount element?
This has to do with the namespaces used maybe?
If you are only interested if any RequestedAmount element with value 5000 exists in the soap message, you could just use this I think:
#"//RequestedAmount/text() = 5000"
I'm have a problem with a SOAP header created in my C# client. The server is sending back the error
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<s:Header xmlns:s="http://www.w3.org/2003/05/soap-envelope" />
<soap:Body>
<soap:Fault>
<soap:Code>
<soap:Value>soap:MustUnderstand</soap:Value>
</soap:Code>
<soap:Reason>
<soap:Text xml:lang="en">MustUnderstand headers: [{http://www.w3.org/2005/08/addressing}To] are not understood.</soap:Text>
</soap:Reason>
</soap:Fault>
</soap:Body>
</soap:Envelope>
I have been under the impression that I have been removing all SOAP headers with the following code.
internal class CustomMessageInspector : IEndpointBehavior, IClientMessageInspector
{
public object BeforeSendRequest( ref Message request, IClientChannel channel )
{
request.Headers.Clear();
return null;
}
...
}
However, after activating System.ServiceModel.MessageLogging in the app.config, (WCF - Inspect the messages being sent/received?), I see that the server is correct - lo and behold there is a "To" header with "mustUnderstand" set to 1 :
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:To s:mustUnderstand="1">https://ws-single-transactions-int-bp.nmvs.eu:8443/WS_SINGLE_TRANSACTIONS_V1/SinglePackServiceV30</a:To>
</s:Header>
Any thoughts how I can prevent this header from being added?
Many thanks.
In case it helps anybody else, I have found a solution. In fact Nicolas Giannone provided all the necessary code here WSHttpBinding in .NetStandard or .NET core . What we can do is to replace the WSHttpBinding with a custom binding based on the WSHttpBinding, and then replace the TextMessageEncodingBindingElement with one with no addressing. Here's the code :
string endPoint = myConfig.SinglePackServicesEndPoint;
//Defines a secure binding with certificate authentication
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
// create a new binding based on the existing binding
var customTransportSecurityBinding = new CustomBinding( binding );
// locate the TextMessageEncodingBindingElement - that's the party guilty of the inclusion of the "To"
var ele = customTransportSecurityBinding.Elements.FirstOrDefault( x=>x is TextMessageEncodingBindingElement );
if( ele != null )
{
// and replace it with a version with no addressing
// replace {Soap12 (http://www.w3.org/2003/05/soap-envelope) Addressing10 (http://www.w3.org/2005/08/addressing)}
// with {Soap12 (http://www.w3.org/2003/05/soap-envelope) AddressingNone (http://schemas.microsoft.com/ws/2005/05/addressing/none)}
int index = customTransportSecurityBinding.Elements.IndexOf( ele );
var textBindingElement = new TextMessageEncodingBindingElement
{
MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None)
};
customTransportSecurityBinding.Elements[index] = textBindingElement;
}
I have been trying to add a header to SOAP request as follows
<soapenv:Header>
<UsernameToken xmlns="http://test.com/webservices">username</UsernameToken>
<PasswordText xmlns="http://test.com/webservices">password</PasswordText>
<SessionType xmlns="http://test.com/webservices">None</SessionType>
</soapenv:Header>
I have found suggestions to use SoapHeader to include header values, but introduces another level such as
<soapenv:Header>
<CustomHeader>
<UsernameToken xmlns="http://test.com/webservices">username</UsernameToken>
<PasswordText xmlns="http://test.com/webservices">password</PasswordText>
<SessionType xmlns="http://test.com/webservices">None</SessionType>
</CustomHeader>
</soapenv:Header>
Can anyone suggest how I can form a request without CustomHeader.
Try to use this one
private static void Main()
{
using (var client = new ServiceClient())
using (var scope = new OperationContextScope(client.InnerChannel))
{
MessageHeader usernameTokenHeader = MessageHeader.CreateHeader("UsernameToken",
"http://test.com/webservices", "username");
OperationContext.Current.OutgoingMessageHeaders.Add(usernameTokenHeader);
MessageHeader passwordTextHeader = MessageHeader.CreateHeader("PasswordText",
"http://test.com/webservices", "password");
OperationContext.Current.OutgoingMessageHeaders.Add(passwordTextHeader);
MessageHeader sessionTypeHeader = MessageHeader.CreateHeader("SessionType",
"http://test.com/webservices", "None");
OperationContext.Current.OutgoingMessageHeaders.Add(sessionTypeHeader);
string result = client.GetData(1);
Console.WriteLine(result);
}
Console.ReadKey();
}
The Service Trace viewer shows following
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<UsernameToken xmlns="http://test.com/webservices">username</UsernameToken>
<PasswordText xmlns="http://test.com/webservices">password</PasswordText>
<SessionType xmlns="http://test.com/webservices">None</SessionType>
<To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://localhost:13332/Service1.svc</To>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IService/GetData</Action>
</s:Header>
</s:Envelope>
Take a look OperationContextScope for more info
I'm trying to connect to a web service, written in Java, but there's something I can't figure out.
Using WCF and a customBinding, almost everything seems to be fine, except one part of the SOAP message, as it's missing the Nonce and Created part nodes.
Obviously I'm missing something, so if you could point me into the right direction, it'd be much appreciated.
Here's the custom binding:
<binding name="CustomHTTPBinding">
<security includeTimestamp="false" authenticationMode="UserNameOverTransport" defaultAlgorithmSuite="Basic256" requireDerivedKeys="True"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
</security>
<textMessageEncoding maxReadPoolSize="211" maxWritePoolSize="2132" messageVersion="Soap11"
writeEncoding="utf-8"/>
<httpsTransport />
</binding>
And here's the relevant part of the message:
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<o:UsernameToken u:Id="uuid-c306efd1-e84c-410e-a2ad-1046b368582e-1">
<o:Username>
<!-- Removed-->
</o:Username>
<o:Password>
<!-- Removed-->
</o:Password>
</o:UsernameToken>
</o:Security>
And this's how it should look:
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-25763165">
<wsse:Username>..</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">..</wsse:Password>
<wsse:Nonce>6ApOnLn5Aq9KSH46pzzcZA==</wsse:Nonce>
<wsu:Created>2009-05-13T18:59:23.309Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
So the question is: How could I introduce the Nonce and Created elements inside the security part?
To create the nonce, I had to change a few things
First, added a custom binding in my config
<system.serviceModel>
<bindings>
<customBinding>
<binding name="myCustomBindingConfig">
<security includeTimestamp="false"
authenticationMode="UserNameOverTransport"
defaultAlgorithmSuite="Basic256"
requireDerivedKeys="true"
messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10">
</security>
<textMessageEncoding messageVersion="Soap11"></textMessageEncoding>
<httpsTransport maxReceivedMessageSize="2000000000" />
</binding>
</customBinding>
</bindings>
</system.serviceModel>
<client>
<endpoint address="https://..." [other tags]
binding="customBinding" bindingConfiguration="OrangeLeapCustomBindingConfig"/>
</client>
Then, take this code found here: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/4df3354f-0627-42d9-b5fb-6e880b60f8ee
and modify it to create the nonce (just a random hash, base-64 encoded)
protected override void WriteTokenCore(System.Xml.XmlWriter writer, System.IdentityModel.Tokens.SecurityToken token)
{
Random r = new Random();
string tokennamespace = "o";
DateTime created = DateTime.Now;
string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
string nonce = Convert.ToBase64String(Encoding.ASCII.GetBytes(SHA1Encrypt(created + r.Next().ToString())));
System.IdentityModel.Tokens.UserNameSecurityToken unToken = (System.IdentityModel.Tokens.UserNameSecurityToken)token;
writer.WriteRaw(String.Format(
"<{0}:UsernameToken u:Id=\"" + token.Id + "\" xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
"<{0}:Username>" + unToken.UserName + "</{0}:Username>" +
"<{0}:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText\">" +
unToken.Password + "</{0}:Password>" +
"<{0}:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" +
nonce + "</{0}:Nonce>" +
"<u:Created>" + createdStr + "</u:Created></{0}:UsernameToken>", tokennamespace));
}
protected String ByteArrayToString(byte[] inputArray)
{
StringBuilder output = new StringBuilder("");
for (int i = 0; i < inputArray.Length; i++)
{
output.Append(inputArray[i].ToString("X2"));
}
return output.ToString();
}
protected String SHA1Encrypt(String phrase)
{
UTF8Encoding encoder = new UTF8Encoding();
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(encoder.GetBytes(phrase));
return ByteArrayToString(hashedDataBytes);
}
I had the same problem. Instead of the custom token serializer I used a MessageInspector to add the correct UsernameToken in the BeforeSendRequest method. I then used a custom behavior to apply the fix.
The entire process is documented (with a demo project) in my blog post Supporting the WS-I Basic Profile Password Digest in a WCF client proxy. Alternatively, you can just read the PDF.
If you want to follow my progress through to the solution, you'll find it on StackOverflow titled, "Error in WCF client consuming Axis 2 web service with WS-Security UsernameToken PasswordDigest authentication scheme":
It's worth pointing out that Rick Strahl made a blog post (which he references this question) where he explains it all quite clearly and offers solutions for both just Password and also PasswordDigest.
I post this because I found this article originally, couldn't really follow it, and found Rick's post much later. This might save some people some time.
WCF WSSecurity and WSE Nonce Authentication
I also had to put a UserNameHeader segment in the SOAP message header:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:bar:services" xmlns:efm="urn:bar:services">
<soapenv:Header>
<efm:UserNameHeader>
<UserName>foouser</UserName>
<Password>foopass</Password>
</efm:UserNameHeader>
</soapenv:Header>
<soapenv:Body>
<urn:GetUserList/>
</soapenv:Body>
</soapenv:Envelope>
This was accomplished with a custom message header:
public class UserNamePasswordHeader : MessageHeader
{
private readonly string _serviceUserEmail;
private readonly string _serviceUserPassword;
public UserNamePasswordHeader(string serviceUserEmail, string serviceUserPassword)
{
this._serviceUserEmail = serviceUserEmail;
this._serviceUserPassword = serviceUserPassword;
}
public override string Name
{
get { return "UserNameHeader"; }
}
public override string Namespace
{
get { return "urn:bar:services"; }
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteElementString("UserName", _serviceUserEmail);
writer.WriteElementString("Password", _serviceUserPassword);
}
}
Other tags, such as Nonce and Created, could easily be added.
The class is used as follows:
var service = new BarServiceClient();
service.ClientCredentials.ClientCertificate.Certificate = MessageSigningCertificate;
using (new OperationContextScope(service.InnerChannel))
{
OperationContext.Current.OutgoingMessageHeaders.Add(
new UserNamePasswordHeader(serviceUserEmail, serviceUserPassword));
try
{
var response = service.GetUserList();
return response;
}
finally
{
service.Close();
}
}
Note: MessageSigningCertificate is an X.509 certificate, I read it from a file:
private static X509Certificate2 LoadCertificateFromFile(string pfxFilePath, string privateKeyPassword)
{
// Load the certificate from a file, specifying the password
var certificate = new X509Certificate2(pfxFilePath, privateKeyPassword);
return certificate;
}