You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将使用WSE的.NET Framework SOAP客户端迁移至.NET5/.NET6问题咨询

问题根因与解决方案

问题1:使用WCF ClientBase调用报「无安全头」错误

根因

.NET5+ 自带的System.ServiceModel(WCF Core)默认的用户名凭证逻辑仅支持传输层Basic认证,不会自动在SOAP消息头中构造符合WS-Security规范的PasswordDigest格式UsernameToken,仅配置ClientCredentials.UserName无法满足服务端的安全头要求。

解决方案:自定义消息拦截器注入安全头

  1. 实现客户端消息拦截器,在请求发送前手动构造符合规范的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)
    {
        // 无需处理响应可留空
    }
}
  1. 实现端点行为,将拦截器注入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) { }
}
  1. 调用时注入自定义行为即可:
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错误

根因

你原有代码存在三个关键错误:

  1. 修改完XmlDocument的节点内容后,没有将修改后的XML导出为字符串,仍使用原始模板内容发起请求
  2. PasswordDigest计算逻辑错误:你拼接的是Nonce的Base64编码字符串,规范要求拼接Nonce的原始字节
  3. 时间戳未使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 09:09:07