【问题标题】:How to implement a Security token in a WCF soap response?如何在 WCF 肥皂响应中实现安全令牌?
【发布时间】:2012-05-01 07:55:25
【问题描述】:

我正在实施来自银行的 API,他们需要提供安全令牌。在每条soap消息的标题中都有如下内容:

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

根据他们的文档,我需要在每条消息的正文上生成一个 8 字节的 MAC 值。 MAC由CBC-MAC算法和DES作为分组密码生成。每个消息的soapenv:Body标签的内容被用作MAC计算的数据。

所以我的问题是如何让 WCF 做到这一点?我已将以下代码放在一起创建 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;
        }

任何帮助将不胜感激。

更多信息: 银行提供了一个 WSDL,我将其用作服务参考。发送的响应示例:

[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;

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";
        }

【问题讨论】:

    标签: c# wcf security soap token


    【解决方案1】:

    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)
            {
            }
        }
    

    【讨论】:

      【解决方案2】:

      我最近不得不做这样的事情,我最终做的是创建一个实现 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 是我在互联网上某个地方找到的一个类(不幸的是,现在似乎找不到它),它刚刚起作用。这是代码:

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

      然后通过配置文件分 3 步将其连接到 WCF 客户端端点(全部在 system.serviceModel 节点下:

      注册扩展

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

      使用它创建端点行为

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

      将您的端点行为附加到您的客户端端点

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

      希望对你有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多