简体   繁体   English

如何在WCF soap响应中实现安全性令牌?

[英]How to implement a Security token in a WCF soap response?

I am implementing an API from a bank and they require a security token to be provided. 我正在从银行实施API,他们需要提供安全令牌。 In the header of every soap message there is something which looks as follows: 在每个肥皂消息的标题中,有一些看起来如下:

<soapenv:Header>
  <tpw:BinarySecurityToken ValueType="MAC" Id="DesMacToken" EncodingType="Base64" Value="**xvz**"/>
</soapenv:Header>

According to their documentation I need to generate an 8 byte MAC value on the body of each message. 根据他们的文档,我需要在每条消息的主体上生成一个8字节的MAC值。 The MAC is generated by the CBC-MAC algorithm and DES as the block cipher. MAC由CBC-MAC算法生成,DES由块密码生成。 The contents of the soapenv:Body tag of each message is used as the data for the MAC calculation. 每条消息的soapenv:Body标签的内容用作MAC计算的数据。

So my question is how do I get WCF to do this? 所以我的问题是我如何让WCF这样做? I have put the following code together to create the MAC value, but am unsure how to get this into the header of every message. 我已将以下代码放在一起以创建MAC值,但我不确定如何将其放入每条消息的标头中。

private string GenerateMAC(string SoapXML)
        {
            ASCIIEncoding encoding = new ASCIIEncoding();

            //Convert from Hex to Bin
            byte[] Key = StringToByteArray(HexKey);
            //Convert String to Bytes
            byte[] XML = encoding.GetBytes(SoapXML);

            //Perform the Mac goodies
            MACTripleDES DesMac = new MACTripleDES(Key);
            byte[] Mac = DesMac.ComputeHash(XML);

            //Base64 the Mac
            string Base64Mac = Convert.ToBase64String(Mac);

            return Base64Mac;
        }

        public static byte[] StringToByteArray(string Hex)
        {
            if (Hex.Length % 2 != 0)
            {
                throw new ArgumentException();
            }

            byte[] HexAsBin = new byte[Hex.Length / 2];
            for (int index = 0; index < HexAsBin.Length; index++)
            {
                string bytevalue = Hex.Substring(index * 2, 2);
                HexAsBin[index] = Convert.ToByte(bytevalue, 16);
            }

            return HexAsBin;
        }

Any help will be greatly appreciated. 任何帮助将不胜感激。

More info: The bank has provided a WSDL which I have used as a service reference. 更多信息:银行提供了一个我用作服务参考的WSDL。 An example of a response that is sent: 发送的响应示例:

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.MessageContractAttribute(WrapperName="LogonRequest", WrapperNamespace="http://webservice.com", IsWrapped=true)]
public partial class LogonRequest {

    [System.ServiceModel.MessageHeaderAttribute(Namespace="http://webservice.com")]
    public DataAccess.BankService.BinarySecurityToken BinarySecurityToken;

The BinarySecurityToken (that goes in the header) looks as follows: BinarySecurityToken(在标题中)如下所示:

 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.233")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(AnonymousType=true, Namespace="http://webservice.com")]
    public partial class BinarySecurityToken : object, System.ComponentModel.INotifyPropertyChanged {

        private string valueTypeField;

        private string idField;

        private string encodingTypeField;

        private string valueField;

        public BinarySecurityToken() {
            this.valueTypeField = "MAC";
            this.idField = "DesMacToken";
            this.encodingTypeField = "Base64";
        }

I had to do something like this recently and what I ended up doing was creating a behaviour that implemented IClientMessageInspector and used the BeforeSendRequest method to create data for my header and then populate it into the SOAP request. 我最近不得不做这样的事情,我最终做的是创建一个实现IClientMessageInspector的行为,并使用BeforeSendRequest方法为我的头创建数据,然后将其填充到SOAP请求中。

public class SoapHeaderBehaviour : BehaviorExtensionElement, IClientMessageInspector
{
    public void AfterReceiveReply(ref Message reply, object correlationState) { }
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    { 
        var security = new Security();   // details irrelevant
        var messageHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", security, new ConcreteXmlObjectSerializer(typeof(Security)), true);
        request.Headers.Add(messageHeader);

        return null;
    }

    protected override object CreateBehavior() { return new SoapHeaderBehaviour(); }
    public override Type BehaviorType { get { return GetType(); } }
}

ConcreteXmlObjectSerializer is a class I found on the internet somewhere (unfortunately can't seem to find it right now) that just worked. ConcreteXmlObjectSerializer是我在互联网上找到的一个类(很遗憾,现在似乎无法找到它)刚刚起作用。 Here is the code for that: 这是代码:

public class ConcreteXmlObjectSerializer : XmlObjectSerializer
{
    readonly Type objectType;
    XmlSerializer serializer;

    public ConcreteXmlObjectSerializer(Type objectType)
        : this(objectType, null, null)
    {
    }

    public ConcreteXmlObjectSerializer(Type objectType, string wrapperName, string wrapperNamespace)
    {
        if (objectType == null)
            throw new ArgumentNullException("objectType");
        if ((wrapperName == null) != (wrapperNamespace == null))
            throw new ArgumentException("wrapperName and wrapperNamespace must be either both null or both non-null.");
        if (wrapperName == string.Empty)
            throw new ArgumentException("Cannot be the empty string.", "wrapperName");

        this.objectType = objectType;
        if (wrapperName != null)
        {
            XmlRootAttribute root = new XmlRootAttribute(wrapperName);
            root.Namespace = wrapperNamespace;
            this.serializer = new XmlSerializer(objectType, root);
        }
        else
            this.serializer = new XmlSerializer(objectType);
    }

    public override bool IsStartObject(XmlDictionaryReader reader)
    {
        throw new NotImplementedException();
    }

    public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
    {
        Debug.Assert(serializer != null);
        if (reader == null) throw new ArgumentNullException("reader");
        if (!verifyObjectName)
            throw new NotSupportedException();

        return serializer.Deserialize(reader);
    }

    public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
    {
        throw new NotImplementedException();
    }

    public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
    {
        if (writer == null) throw new ArgumentNullException("writer");
        if (writer.WriteState != WriteState.Element)
            throw new SerializationException(string.Format("WriteState '{0}' not valid. Caller must write start element before serializing in contentOnly mode.",
                writer.WriteState));
        using (MemoryStream memoryStream = new MemoryStream())
        {
            using (XmlDictionaryWriter bufferWriter = XmlDictionaryWriter.CreateTextWriter(memoryStream, Encoding.UTF8))
            {
                serializer.Serialize(bufferWriter, graph);
                bufferWriter.Flush();
                memoryStream.Position = 0;
                using (XmlReader reader = new XmlTextReader(memoryStream))
                {
                    reader.MoveToContent();
                    writer.WriteAttributes(reader, false);
                    if (reader.Read()) // move off start node (we want to skip it)
                    {
                        while (reader.NodeType != XmlNodeType.EndElement) // also skip end node.
                            writer.WriteNode(reader, false); // this will take us to the start of the next child node, or the end node.
                        reader.ReadEndElement(); // not necessary, but clean
                    }
                }
            }
        }
    }

    public override void WriteEndObject(XmlDictionaryWriter writer)
    {
        throw new NotImplementedException();
    }

    public override void WriteObject(XmlDictionaryWriter writer, object graph)
    {
        Debug.Assert(serializer != null);
        if (writer == null) throw new ArgumentNullException("writer");
        serializer.Serialize(writer, graph);
    }
}

This is then hooked into the WCF client endpoint via the config file in 3 steps (all under the system.serviceModel node: 然后通过配置文件以3个步骤将其挂接到WCF客户端端点(所有这些都在system.serviceModel节点下:

Register the extension 注册分机号

<extensions>
  <behaviorExtensions>
    <add name="ClientSoapHeaderAdderBehaviour"
        type="MyNamespace.SoapHeaderBehaviour, MyAssembly, Version=My.Version, Culture=neutral, PublicKeyToken=null" />
  </behaviorExtensions>
</extensions>

Create an endpoint behaviour using it 使用它创建端点行为

<behaviors>
  <endpointBehaviors>
    <behavior name="MyEndpointBehaviours">
      <ClientSoapHeaderAdderBehaviour />
    </behavior>
  </endpointBehaviors>
</behaviors>

Attach your endpoint behaviour to your client endpoint 将端点行为附加到客户端端点

<client>
  <endpoint address="blah" binding="basicHttpBinding"
    bindingConfiguration="blah" contract="blah"
    name="blah"
    behaviorConfiguration="MyEndpointBehaviours"/>
</client>

Hope this helps you. 希望这对你有所帮助。

DavidJones had the right answer, but I wanted to post my class in case anyone else needs to do something similar: DavidJones有正确的答案,但我想发布我的课程以防其他人需要做类似的事情:

 public class SoapHeaderBehaviour : BehaviorExtensionElement, IClientMessageInspector, IEndpointBehavior
    {
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
        }

        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            try
            {
                // Get the request into an XDocument
                var memoryStream = new MemoryStream();

                var writer = XmlDictionaryWriter.CreateTextWriter(memoryStream);
                request.WriteMessage(writer);
                writer.Flush();
                memoryStream.Position = 0;
                var xmlDoc = new XmlDocument();
                xmlDoc.Load(memoryStream);

                // get the body tag
                XmlNode bodyNode = FindNode("body", xmlDoc);
                Debug.Assert(bodyNode != null, "Unable to find the BODY in the SOAP message");
                if (bodyNode != null)
                {
                    string MAC = GenerateMAC(bodyNode.InnerXml);

                    // replace the relevant item in the header
                    XmlNode tokenNode = FindNode("binarysecuritytoken", xmlDoc);
                    Debug.Assert(tokenNode != null, "Unable to find the BinarySecurityToken in the SOAP message");

                    if (tokenNode != null)
                    {
                        tokenNode.Attributes["Value"].Value = MAC;

                        // recreate the request
                        memoryStream = new MemoryStream();
                        writer = XmlDictionaryWriter.CreateTextWriter(memoryStream);
                        xmlDoc.WriteTo(writer);
                        writer.Flush();
                        memoryStream.Position = 0;
                        var reader = XmlDictionaryReader.CreateTextReader(memoryStream, XmlDictionaryReaderQuotas.Max);
                        var newRequest = Message.CreateMessage(reader, int.MaxValue, request.Version);
                        request = newRequest;
                    }
                }
            }
            catch (Exception ex)
            {
            }

            return null;
        }

        private XmlNode FindNode(string name, XmlDocument xmlDoc)
        {
            XmlNode node = null;
            for (int i = 0; i < xmlDoc.ChildNodes.Count; i++)
            {
                node = FindNode(name, xmlDoc.ChildNodes[i]);
                if (node != null)
                    break;
            }

            return node;
        }

        private XmlNode FindNode(string name, XmlNode parentNode)
        {
            if (parentNode != null && parentNode.Name.ToLower().Contains(name))
            {
                return parentNode;
            }

            XmlNode childNode = null;
            for (int i = 0; i < parentNode.ChildNodes.Count; i++)
            {
                childNode = FindNode(name, parentNode.ChildNodes[i]);
                if (childNode != null)
                    break;
            }

            return childNode;
        }

        private string GenerateMAC(string soapXML)
        {
            // get the key from the web.config file
            var key = ConfigurationManager.AppSettings["Key"];

            ASCIIEncoding encoding = new ASCIIEncoding();

            //Convert from Hex to Bin
            byte[] keyBytes = StringToByteArray(key);
            //Convert String to Bytes
            byte[] xmlBytes = encoding.GetBytes(soapXML);

            //Perform the Mac goodies
            MACTripleDES desMac = new MACTripleDES(keyBytes);
            byte[] macBytes = desMac.ComputeHash(xmlBytes);

            //Base64 the Mac
            string base64Mac = Convert.ToBase64String(macBytes);
            return base64Mac;
        }

        private static byte[] StringToByteArray(string hex)
        {
            if (hex.Length % 2 != 0)
            {
                throw new ArgumentException();
            }

            byte[] hexBytes = new byte[hex.Length / 2];
            for (int index = 0; index < hexBytes.Length; index++)
            {
                string bytevalue = hex.Substring(index * 2, 2);
                hexBytes[index] = Convert.ToByte(bytevalue, 16);
            }

            return hexBytes;
        }

        protected override object CreateBehavior()
        {
            return new SoapHeaderBehaviour();
        }

        public override Type BehaviorType
        {
            get
            {
                return GetType();
            }
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime behavior)
        {
            behavior.MessageInspectors.Add(this);
            // behavior.MessageInspectors.Add(new FaultMessageInspector());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        public void Validate(ServiceEndpoint serviceEndpoint)
        {
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM