将使用WSE的.NET Framework SOAP客户端迁移至.NET5/.NET6问题咨询
问题根因与解决方案
问题1:使用WCF ClientBase调用报「无安全头」错误
根因
.NET5+ 自带的System.ServiceModel(WCF Core)默认的用户名凭证逻辑仅支持传输层Basic认证,不会自动在SOAP消息头中构造符合WS-Security规范的PasswordDigest格式UsernameToken,仅配置ClientCredentials.UserName无法满足服务端的安全头要求。
解决方案:自定义消息拦截器注入安全头
- 实现客户端消息拦截器,在请求发送前手动构造符合规范的WS-Security头:
public class WssUsernameTokenDigestInspector : IClientMessageInspector { private readonly string _username; private readonly string _password; public WssUsernameTokenDigestInspector(string username, string password) { _username = username; _password = password; } public object BeforeSendRequest(ref Message request, IClientChannel channel) { // 生成16位随机Nonce var nonceBytes = new byte[16]; RandomNumberGenerator.Fill(nonceBytes); var nonceBase64 = Convert.ToBase64String(nonceBytes); // 生成UTC格式时间戳,带毫秒兼容多数服务 var created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); var createdBytes = Encoding.UTF8.GetBytes(created); var passwordBytes = Encoding.UTF8.GetBytes(_password); // 按规范计算PasswordDigest = Base64(SHA1(原始Nonce字节 + Created字节 + 密码字节)) var combinedBytes = new byte[nonceBytes.Length + createdBytes.Length + passwordBytes.Length]; Buffer.BlockCopy(nonceBytes, 0, combinedBytes, 0, nonceBytes.Length); Buffer.BlockCopy(createdBytes, 0, combinedBytes, nonceBytes.Length, createdBytes.Length); Buffer.BlockCopy(passwordBytes, 0, combinedBytes, nonceBytes.Length + createdBytes.Length, passwordBytes.Length); using var sha1 = SHA1.Create(); var digestBytes = sha1.ComputeHash(combinedBytes); var passwordDigest = Convert.ToBase64String(digestBytes); // 构造安全头XML结构 XNamespace wsseNs = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; XNamespace wsuNs = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; XNamespace soapNs = "http://schemas.xmlsoap.org/soap/envelope/"; var securityElement = new XElement(wsseNs + "Security", new XAttribute(soapNs + "mustUnderstand", "1"), new XElement(wsseNs + "UsernameToken", new XElement(wsseNs + "Username", _username), new XElement(wsseNs + "Password", passwordDigest, new XAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-usernametoken-profile-1.0#PasswordDigest")), new XElement(wsseNs + "Nonce", nonceBase64, new XAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soapmessage-security-1.0#Base64Binary")), new XElement(wsuNs + "Created", created) ) ); // 将安全头添加到请求消息中 var ms = new MemoryStream(); using var xmlWriter = XmlWriter.Create(ms); securityElement.WriteTo(xmlWriter); xmlWriter.Flush(); ms.Position = 0; using var xmlReader = XmlReader.Create(ms); var securityHeader = MessageHeader.CreateHeader("Security", wsseNs.NamespaceName, XElement.Load(xmlReader), true); request.Headers.Add(securityHeader); return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { // 无需处理响应可留空 } }
- 实现端点行为,将拦截器注入WCF客户端运行时:
public class WssUsernameTokenDigestBehavior : IEndpointBehavior { private readonly string _username; private readonly string _password; public WssUsernameTokenDigestBehavior(string username, string password) { _username = username; _password = password; } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.ClientMessageInspectors.Add(new WssUsernameTokenDigestInspector(_username, _password)); } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } }
- 调用时注入自定义行为即可:
var client = new MyService(ReinsServicesClient.EndpointConfiguration.ReinsServicesSoap11, ReinsUrl); client.Endpoint.EndpointBehaviors.Add(new WssUsernameTokenDigestBehavior(DmsUsername, DmsPassword)); var myRequest = new Request(); var response = client.WsSearchDmsDocument(myRequest);
问题2:手动构造SOAP用HttpClient调用返回500错误
根因
你原有代码存在三个关键错误:
- 修改完
XmlDocument的节点内容后,没有将修改后的XML导出为字符串,仍使用原始模板内容发起请求 - PasswordDigest计算逻辑错误:你拼接的是Nonce的Base64编码字符串,规范要求拼接Nonce的原始字节
- 时间戳未使用UTC时间,部分服务对时区和时间格式要求严格
修正后的代码:
string soapTemplate = File.ReadAllText("soap.txt"); XmlDocument document = new XmlDocument(); document.LoadXml(soapTemplate); XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable); manager.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); manager.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); // 构造安全参数 var nonceBytes = new byte[16]; RandomNumberGenerator.Fill(nonceBytes); string nonceBase64 = Convert.ToBase64String(nonceBytes); string created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); byte[] createdBytes = Encoding.UTF8.GetBytes(created); byte[] passwordBytes = Encoding.UTF8.GetBytes(DmsPassword); // 正确计算PasswordDigest byte[] combinedBytes = new byte[nonceBytes.Length + createdBytes.Length + passwordBytes.Length]; Buffer.BlockCopy(nonceBytes, 0, combinedBytes, 0, nonceBytes.Length); Buffer.BlockCopy(createdBytes, 0, combinedBytes, nonceBytes.Length, createdBytes.Length); Buffer.BlockCopy(passwordBytes, 0, combinedBytes, nonceBytes.Length + createdBytes.Length, passwordBytes.Length); using var sha1 = SHA1.Create(); string passwordDigest = Convert.ToBase64String(sha1.ComputeHash(combinedBytes)); // 替换模板内容 document.SelectSingleNode("//wsse:UsernameToken/wsu:Created", manager).InnerText = created; document.SelectSingleNode("//wsse:UsernameToken/wsse:Password", manager).InnerText = passwordDigest; document.SelectSingleNode("//wsse:UsernameToken/wsse:Nonce", manager).InnerText = nonceBase64; // 关键:导出修改后的完整SOAP内容 string finalSoap = document.OuterXml; using var httpClient = new HttpClient(); var request = new HttpRequestMessage(HttpMethod.Post, ReinsUrl) { Content = new StringContent(finalSoap, Encoding.UTF8, "text/xml") }; // 头信息与SoapUI保持完全一致即可 request.Headers.Add("SOAPAction", ""); var response = httpClient.Send(request); response.EnsureSuccessStatusCode();
内容的提问来源于stack exchange,提问作者Nigel Findlater
相关产品推荐
相关产品推荐

