ASP.NET Core 3.1如何生成带wsse头的XML请求
ASP.NET Core 3.1生成带WSSE标签的SOAP XML问题
我们用ASP.NET Core 3.1生成XML调用WSDL服务,手动编写的XML在Postman中可正常调用,但程序生成的XML缺失wsse命名空间前缀,结构不符合要求,导致调用失败。
正确XML示例
<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soap:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1"> <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="XXXX"> <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">XXXX</wsse:Password> </wsse:UsernameToken> </wsse:Security> </soap:Header> <soap:Body xmlns:ns1="http://www.opentravel.org/OTA/2003/05"> <ns1:OTA_AirAvailRQ EchoToken="11868765275150-1300257933" PrimaryLangID="en-us" SequenceNmbr="1" TimeStamp="2012-08-27T03:00:23" Version="20061.00" Target="TEST"> <ns1:POS> <ns1:Source TerminalID="XXXX"> <ns1:RequestorID ID="XXXX" Type="XXXX" /> <ns1:BookingChannel Type="12" /> </ns1:Source> </ns1:POS> <ns1:OriginDestinationInformation> <ns1:DepartureDateTime WindowAfter="P1D" WindowBefore="P1D">2023-01-20T10:00:00</ns1:DepartureDateTime> <ns1:OriginLocation LocationCode="BOM" /> <ns1:DestinationLocation LocationCode="SHJ" /> </ns1:OriginDestinationInformation> <ns1:OriginDestinationInformation> <ns1:DepartureDateTime WindowAfter="P1D" WindowBefore="P1D">2023-01-25T10:00:00</ns1:DepartureDateTime> <ns1:OriginLocation LocationCode="SHJ" /> <ns1:DestinationLocation LocationCode="BOM" /> </ns1:OriginDestinationInformation> <ns1:TravelerInfoSummary> <ns1:AirTravelerAvail> <ns1:PassengerTypeQuantity Code="ADT" Quantity="3" /> <ns1:PassengerTypeQuantity Code="CHD" Quantity="1" /> </ns1:AirTravelerAvail> </ns1:TravelerInfoSummary> </ns1:OTA_AirAvailRQ> </soap:Body> </soap:Envelope>
程序生成的错误XML示例
<?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" soap="http://schemas.xmlsoap.org/soap/envelope/" xsd="http://www.w3.org/2001/XMLSchema" xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opentravel.org/OTA/2003/05"> <Header> <Security mustUnderstand="1" wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <UsernameToken Id="xxx" wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xs"> <Username>xx</Username> <Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">xxx</Password> </UsernameToken> </Security> </Header> <Body ns2="http://www.opentravel.org/OTA/2003/05"> <OTA_AirAvailRQ EchoToken="11868765275150-1300257933" PrimaryLangID="en-us" SequenceNmbr="1" Target="xx" TimeStamp="2023-01-14T12:01:53.3258485+03:00" Version="20061"> <POS> <Source TerminalID="xx"> <RequestorID ID="xx" Type="4" /> <BookingChannel Type="12" /> </Source> </POS> <OriginDestinationInformation> <DepartureDateTime WindowAfter="P1D" WindowBefore="P1D">2023-01-20T10:00:00</DepartureDateTime> <OriginLocation LocationCode="BOM" /> <DestinationLocation LocationCode="SHJ" /> </OriginDestinationInformation> <TravelerInfoSummary> <AirTravelerAvail> <PassengerTypeQuantity Code="ADT" Quantity="1" /> </AirTravelerAvail> </TravelerInfoSummary> </OTA_AirAvailRQ> </Body> </Envelope>
尝试过的方案
方案1:WCF客户端
使用BasicHttpBinding配置,但报错“Compiling JScript/CSharp scripts is not supported”,代码如下:
System.ServiceModel.BasicHttpBinding binding = new System.ServiceModel.BasicHttpBinding(); binding.MaxBufferSize = int.MaxValue; binding.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max; binding.MaxReceivedMessageSize = int.MaxValue; binding.AllowCookies = true; binding.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport; binding.TransferMode = System.ServiceModel.TransferMode.Buffered; binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic; ServiceHelper _ServiceHelper = new ServiceHelper(); var address = new EndpointAddress("https://airarabia.isaaviations.com/webservices/services/AAResWebServices"); var client = new AAResWebServicesClient((Binding)binding, address); client.ClientCredentials.UserName.UserName = _ServiceHelper.airarabia9GUserName; client.ClientCredentials.UserName.Password = _ServiceHelper.airarabia9Gpassword; OTA_AirAvailRQ test = new OTA_AirAvailRQ(); OTA_AirAvailRS TEST2; try { var httpHeaders = ReturnHttpHeader(_ServiceHelper.airarabia9GUserName, _ServiceHelper.airarabia9Gpassword); client.Endpoint.EndpointBehaviors.Add(new HttpHeadersEndpointBehavior(httpHeaders)); TEST2 = client.getAvailability(test); } catch (Exception se) { Console.WriteLine("Error : " + se.Message); client.Abort(); return null; }
方案2:手动实体序列化
手动创建实体后用XmlSerializer序列化,生成的XML结构不符合要求,代码如下:
//envelope Containt all the modules and will convert them into XML var stringwriter = new System.IO.StringWriter(); var serializer = new XmlSerializer(typeof(Envelope), "http://www.opentravel.org/OTA/2003/05"); serializer.Serialize(stringwriter, envelope); string strXML = stringwriter.ToString(); strXML = strXML.Replace("utf-16", "utf-8"); var url = _Helper.airarabia9GURL; var client2 = new RestClient(url); var request = new RestRequest(url, Method.POST); #region Headers request.AddHeader("Content-Type", "application/xml"); request.AddHeader("Cookie", "JSESSIONID=0F63BEC1C68DDEC6ADB581CC621E1B8E.demo2144"); #endregion request.AddParameter("application/xml", Mybody, ParameterType.RequestBody); IRestResponse response = client2.Execute(request); Console.WriteLine(response.Content);
可行解决方法
修复WCF客户端方案
问题出在绑定类型和WSSE安全配置上,BasicHttpBinding默认不支持WS-Security,需调整配置或手动添加WSSE头:
1. 改用WSHttpBinding并配置WS-Security
// 使用支持WS-Security的WSHttpBinding var binding = new WSHttpBinding(SecurityMode.Transport); binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName; binding.Security.Message.EstablishSecurityContext = false; binding.MaxBufferSize = int.MaxValue; binding.ReaderQuotas = XmlDictionaryReaderQuotas.Max; binding.MaxReceivedMessageSize = int.MaxValue; binding.AllowCookies = true; var address = new EndpointAddress("https://airarabia.isaaviations.com/webservices/services/AAResWebServices"); var client = new AAResWebServicesClient(binding, address); // 配置用户名密码 client.ClientCredentials.UserName.UserName = _ServiceHelper.airarabia9GUserName; client.ClientCredentials.UserName.Password = _ServiceHelper.airarabia9Gpassword; // 禁用脚本编译解决报错 var metadataBehavior = new ServiceMetadataBehavior { HttpGetEnabled = false }; client.Endpoint.EndpointBehaviors.Add(metadataBehavior); try { OTA_AirAvailRQ test = new OTA_AirAvailRQ(); // 填充test的必要字段 var TEST2 = client.getAvailability(test); client.Close(); return TEST2; } catch (Exception se) { Console.WriteLine("Error : " + se.Message); client.Abort(); return null; }
2. 手动添加WSSE消息头(兼容BasicHttpBinding)
通过自定义MessageInspector构造WSSE头:
public class WsseMessageInspector : IClientMessageInspector { private readonly string _username; private readonly string _password; public WsseMessageInspector(string username, string password) { _username = username; _password = password; } public object BeforeSendRequest(ref Message request, IClientChannel channel) { var writer = new StringWriter(); var xmlWriter = XmlWriter.Create(writer); // 构造WSSE Security头 xmlWriter.WriteStartElement("wsse", "Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); xmlWriter.WriteAttributeString("soap", "mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1"); xmlWriter.WriteStartElement("wsse", "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); xmlWriter.WriteAttributeString("wsu", "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "XXXX"); xmlWriter.WriteElementString("wsse", "Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", _username); xmlWriter.WriteStartElement("wsse", "Password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); xmlWriter.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"); xmlWriter.WriteString(_password); xmlWriter.WriteEndElement(); xmlWriter.WriteEndElement(); xmlWriter.WriteEndElement(); xmlWriter.Flush(); var securityXml = writer.ToString(); var doc = new XmlDocument(); doc.LoadXml(securityXml); var header = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", doc.DocumentElement); request.Headers.Add(header); return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { } } // 注册Inspector的Behavior public class EndpointBehavior : IEndpointBehavior { private readonly IClientMessageInspector _inspector; public EndpointBehavior(IClientMessageInspector inspector) => _inspector = inspector; public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) => clientRuntime.ClientMessageInspectors.Add(_inspector); public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } } // 使用方式 client.Endpoint.EndpointBehaviors.Add(new EndpointBehavior(new WsseMessageInspector(_ServiceHelper.airarabia9GUserName, _ServiceHelper.airarabia9Gpassword)));
修复手动序列化方案
需给实体类添加正确的XML命名空间属性,强制生成指定前缀:
1. 定义带命名空间的实体类
[XmlRoot(ElementName = "Envelope", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")] public class Envelope { [XmlElement(ElementName = "Header", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")] public Header Header { get; set; } [XmlElement(ElementName = "Body", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")] public Body Body { get; set; } // 声明命名空间前缀 [XmlNamespaceDeclarations] public XmlSerializerNamespaces Xmlns { get; set; } = new XmlSerializerNamespaces(new[] { new XmlQualifiedName("soap", "http://schemas.xmlsoap.org/soap/envelope/"), new XmlQualifiedName("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"), new XmlQualifiedName("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"), new XmlQualifiedName("ns1", "http://www.opentravel.org/OTA/2003/05") }); } public class Header { [XmlElement(ElementName = "Security", Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")] public Security Security { get; set; } } public class Security { [XmlAttribute(AttributeName = "mustUnderstand", Namespace = "http://schemas.xmlsoap.org/soap/envelope/")] public string MustUnderstand { get; set; } = "1"; [XmlElement(ElementName = "UsernameToken", Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")] public UsernameToken UsernameToken { get; set; } } public class UsernameToken { [XmlAttribute(AttributeName = "Id", Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")] public string Id { get; set; } [XmlElement(ElementName = "Username", Namespace = "http://docs.oasis-open.org/wss/2004/01/o
相关产品推荐
相关产品推荐

