I'm trying to make a call to a webservice and want to manually add the ws-security headers into the request because .net core 2.2 currently does not support ws-security.
I have created my custom security header class:
public class SoapSecurityHeader : MessageHeader
{
private readonly string _password, _username;
public SoapSecurityHeader(string id, string username, string password)
{
_password = password;
_username = username;
}
public override bool MustUnderstand => true;
public override string Name
{
get { return "Security"; }
}
public override string Namespace
{
get { return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; }
}
protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement("wsse", Name, Namespace);
writer.WriteAttributeString("s", "mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1");
writer.WriteXmlnsAttribute("wsse", Namespace);
}
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
writer.WriteStartElement("wsse", "UsernameToken", Namespace);
writer.WriteAttributeString("wsu", "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "UsernameToken-32");
// Username
writer.WriteStartElement("wsse", "Username", Namespace);
writer.WriteValue(_username);
writer.WriteEndElement();
// Password
writer.WriteStartElement("wsse", "Password", Namespace);
writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
writer.WriteValue(_password);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
And this is my method calling the SOAP service:
public ActionResult<Ted_Result> Get(DateTime dateFrom, DateTime dateTo, int? pageFrom, int? pageTo)
{
BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
EndpointAddress endpointAddress = new EndpointAddress(new Uri("https://localhost/SomeService.svc"));
ChannelFactory<IConnectPublicService> factory = new ChannelFactory<IConnectPublicService>(basicHttpBinding, endpointAddress);
GetContractNoticesResponseMessage result = null;
// Bypass SSL/TLS secure channel validation
#if DEBUG
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
{
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck
};
#endif
// Debugging inspector
factory.Endpoint.EndpointBehaviors.Add(new InspectorBehavior());
IConnectPublicService serviceProxy = factory.CreateChannel();
((ICommunicationObject)serviceProxy).Open();
var opContext = new OperationContext((IClientChannel)serviceProxy);
var soapSecurityHeader = new SoapSecurityHeader("UsernameToken-32", "sampleUsername", "samplePassword123");
// Adding the security header
opContext.OutgoingMessageHeaders.Add(soapSecurityHeader);
var prevOpContext = OperationContext.Current; // Optional if there's no way this might already be set
OperationContext.Current = opContext;
var info = new ExternalIntegrationRequestMessageInfo
{
UserCode = "1000249",
CompanyCode = "200000040"
};
var request = new GetContractNoticesRequestMessage
{
Info = info,
DateFrom = dateFrom,
DateTo = dateTo,
PageFrom = pageFrom,
PageTo = pageTo
};
result = serviceProxy.GetContractNoticesAsync(request).ConfigureAwait(false).GetAwaiter().GetResult();
return Ok(result);
}
If I put a breakpoint inside the inspector at BeforeSendRequest I can see that the security header is added to the request:
<wsse:Security s:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-32" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>sampleUsername</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">samplePassword123</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
And putting a breakpoint inside the inspector at AfterReceiveReply, I get the CORRECT result, but I still get an exception. The result:
<...>
<s:Header>
<...>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>2019-01-11T19:42:53.606Z</u:Created>
<u:Expires>2019-01-11T19:47:53.606Z</u:Expires>
</u:Timestamp>
</o:Security>
</s:Header>
<s:Body>
<GetContractNoticesResponseMessage>
<ContractNotices>....</ContractNotices>
</GetContractNoticesResponseMessage>
</s:Body>
The exception:
An unhandled exception occurred while processing the request.
ProtocolException: 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.
Why do I still get an exception after calling the webservice successfully?
AfterReceiveReply
but that sounds like a bad plan :-) – Mar