Wcf: BasicHttpMessageSecurity

Created on 18 Aug 2017  路  10Comments  路  Source: dotnet/wcf

This is not really a issue, when will BasicHttpMessageSecurity be supported by .net core 2.0?

Most helpful comment

@zhenlan my question was for you. I could help improve your library if needed.

@LEsperanca
To solve this problem, I created my own SOAP Header (as a temporary solution). Find an example below using username/password in clear text but the method will remain the same for other security protocols

    public class SoapSecurityHeader : MessageHeader
    {
        private readonly string _username;
        private readonly string _password;

        public SoapSecurityHeader(string username, string password)
        {
            _username = username;
            _password = password;
        }

        public override string Name { get; } = "Security";

        public override string Namespace { get; } = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";

        private const string WsuNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";

        protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            writer.WriteStartElement("wsse", "UsernameToken", Namespace);
            writer.WriteAttributeString("wsu", "Id", WsuNamespace, Guid.NewGuid().ToString());

            writer.WriteStartElement("wsse", "Username", Namespace);
            writer.WriteValue(_username);
            writer.WriteEndElement();

            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();
        }

        protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            writer.WriteStartElement("wsse", Name, Namespace);
            writer.WriteXmlnsAttribute("wsse", Namespace);
        }
    }

    public class SoapSecurityHeaderInspector : IClientMessageInspector
    {
        private readonly string _username;
        private readonly string _password;

        public SoapSecurityHeaderInspector(string username, string password)
        {
            _username = username;
            _password = password;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {

        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            request.Headers.Add(new SoapSecurityHeader(_username, _password));

            return null;
        }
    }

    public class SoapSecurityHeaderBehavior : IEndpointBehavior
    {
        private readonly string _username;
        private readonly string _password;

        public SoapSecurityHeaderBehavior(string username, string password)
        {
            _username = username;
            _password = password;
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new SoapSecurityHeaderInspector(_username, _password));
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {

        }

        public void Validate(ServiceEndpoint endpoint)
        {

        }
    }

And then, I created a channel factory (used by Autofac)

        private static ChannelFactory<T> CreateSecuredChannel<T>(string url, string username, string password)
        {
            var binding = new BasicHttpsBinding
            {
                TextEncoding = Encoding.UTF8,
                UseDefaultWebProxy = true,
                BypassProxyOnLocal = false,
                Security =
                {
                    Mode = BasicHttpsSecurityMode.Transport,
                    Transport =
                    {
                        ClientCredentialType = HttpClientCredentialType.None,
                        ProxyCredentialType = HttpProxyCredentialType.None
                    }
                }
            };

            var channel = new ChannelFactory<T>(binding, new EndpointAddress(url));
            channel.Endpoint.EndpointBehaviors.Add(new SoapSecurityHeaderBehavior(username, password));

            channel.Credentials.UserName.UserName = username; // not sure if needed
            channel.Credentials.UserName.Password = password;
            return channel;
        }

All 10 comments

@LEsperanca please refer to 2.0 release notes:

  • Message level security is not supported (#3, #4, #8)

At this point we don't have a plan to support message security due to lack of dependent functionalities in System.IdentityModel on .NET Core.

How can we help?

I need to have or emulate BasicHttpMessageSecurity in a .net core service and i don't know how... @hugoterelle

@zhenlan my question was for you. I could help improve your library if needed.

@LEsperanca
To solve this problem, I created my own SOAP Header (as a temporary solution). Find an example below using username/password in clear text but the method will remain the same for other security protocols

    public class SoapSecurityHeader : MessageHeader
    {
        private readonly string _username;
        private readonly string _password;

        public SoapSecurityHeader(string username, string password)
        {
            _username = username;
            _password = password;
        }

        public override string Name { get; } = "Security";

        public override string Namespace { get; } = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";

        private const string WsuNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";

        protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            writer.WriteStartElement("wsse", "UsernameToken", Namespace);
            writer.WriteAttributeString("wsu", "Id", WsuNamespace, Guid.NewGuid().ToString());

            writer.WriteStartElement("wsse", "Username", Namespace);
            writer.WriteValue(_username);
            writer.WriteEndElement();

            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();
        }

        protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
        {
            writer.WriteStartElement("wsse", Name, Namespace);
            writer.WriteXmlnsAttribute("wsse", Namespace);
        }
    }

    public class SoapSecurityHeaderInspector : IClientMessageInspector
    {
        private readonly string _username;
        private readonly string _password;

        public SoapSecurityHeaderInspector(string username, string password)
        {
            _username = username;
            _password = password;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {

        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            request.Headers.Add(new SoapSecurityHeader(_username, _password));

            return null;
        }
    }

    public class SoapSecurityHeaderBehavior : IEndpointBehavior
    {
        private readonly string _username;
        private readonly string _password;

        public SoapSecurityHeaderBehavior(string username, string password)
        {
            _username = username;
            _password = password;
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.ClientMessageInspectors.Add(new SoapSecurityHeaderInspector(_username, _password));
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {

        }

        public void Validate(ServiceEndpoint endpoint)
        {

        }
    }

And then, I created a channel factory (used by Autofac)

        private static ChannelFactory<T> CreateSecuredChannel<T>(string url, string username, string password)
        {
            var binding = new BasicHttpsBinding
            {
                TextEncoding = Encoding.UTF8,
                UseDefaultWebProxy = true,
                BypassProxyOnLocal = false,
                Security =
                {
                    Mode = BasicHttpsSecurityMode.Transport,
                    Transport =
                    {
                        ClientCredentialType = HttpClientCredentialType.None,
                        ProxyCredentialType = HttpProxyCredentialType.None
                    }
                }
            };

            var channel = new ChannelFactory<T>(binding, new EndpointAddress(url));
            channel.Endpoint.EndpointBehaviors.Add(new SoapSecurityHeaderBehavior(username, password));

            channel.Credentials.UserName.UserName = username; // not sure if needed
            channel.Credentials.UserName.Password = password;
            return channel;
        }

@hugoterelle I'm not sure how to actually use the ChannelFactory you've created, can you tell me how I inject this into the WCF service so it uses the correct channelfactory?

@nickcoad
I use autofac to register the channel factory:

builder.Register(c => CreateSecuredChannel("myurl", "myuser", "mypassword")).SingleInstance();
builder.Register(c => c.Resolve>().CreateChannel())
.As()
.UseWcfSafeRelease();

Then, you just need to inject your IMySoapService to use it.

And for the UseWcfSafeRelease used by Autofac, just refer to the issue I raised on the Autofac github
https://github.com/autofac/Autofac.Wcf/issues/11

Any ETA on this issue? This is important feature to support for a lot of WCF service migration projects for us.

@hugoterelle Thank you for your code. I managed to get Basic Auth Message level security working using your code.

@hugoterelle Your code works for me. Thank you very much! Do you wanna make a nuget package till MS do it properly?

@nickcoad FYI You don't have to use Autofac. You able to use any DI or simply:

 client.Endpoint.EndpointBehaviors.Add(new SoapSecurityHeaderBehavior("user", "pass"));

@hugoterelle Thank you very much for your work. It saved my day.

Was this page helpful?
0 / 5 - 0 ratings