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

C#发送带摘要认证Header的SOAP请求失败,寻求排查帮助

C#发送带摘要认证Header的SOAP请求失败,寻求排查帮助

兄弟,看你遇到的这个SOAP认证问题确实头疼,毕竟第一次碰WS-Security很容易踩坑。先帮你理清楚现状:

你需要调用第三方的SOAP服务,对方要求请求必须携带符合OASIS 2004标准的WS-Security Header,其中密码是通过Base64(SHA1(nonce + created + password))生成的摘要。你用SoapUI测试没问题,但自己写的C#代码调用时,一直收到错误:

A security error was encountered when verifying the message Caused by: An error was discovered processing the wsse:Security header.

结合你贴的代码,我发现几个可能的问题点,给你逐个分析并给出解决办法:


问题1:绑定配置冲突,安全模式和凭证类型不匹配

你当前的绑定配置同时设置了Transport.ClientCredentialType = Digest和Message.ClientCredentialType = UserName,这两种配置是冲突的。因为第三方要求的是消息层的WS-Security UsernameToken摘要认证,不是传输层的Digest认证。

修正绑定配置:

// 使用TransportWithMessageCredential模式(因为服务地址是HTTPS)
BasicHttpBinding _binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportWithMessageCredential);
// 消息层凭证类型设为UserName,对应WS-Security的UsernameToken
_binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
// 指定WS-Security版本为WSS10(匹配第三方用的OASIS 2004标准)
var msgSecurity = _binding.Security.Message as BasicHttpMessageSecurity;
msgSecurity.SecurityVersion = SecurityVersion.WSSecurity10;
// 算法套件匹配第三方的SHA1要求
_binding.Security.Message.AlgorithmSuite = SecurityAlgorithmSuite.Basic128Sha1Rsa15;

// 移除Transport层的Digest配置,不需要
// _binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Digest;

问题2:手动添加Header + WCF自动生成Header,导致重复或格式冲突

你代码里同时做了两件事:

  1. 通过ClientCredentials.UserName设置了用户名密码,WCF会自动生成WS-Security Header
  2. 手动调用OperationContext.Current.OutgoingMessageHeaders.Add(GetHeader())添加自定义Header

这会导致请求里出现两个wsse:Security节点,或者手动生成的Header格式不符合第三方要求,直接触发认证错误。

解决办法:

删掉手动添加Header的代码,让WCF自动生成符合标准的WS-Security Header。如果WCF默认生成的格式和第三方要求有差异,再通过自定义扩展调整,不要手动拼接Header(很容易出格式错误)。


问题3:密码摘要的生成顺序不符合第三方要求

第三方明确要求密码摘要的公式是:
PasswordDigest = Base64(SHA1(nonce + created + password))

但WCF默认的密码摘要生成顺序是SHA1(nonce + password + created),这会导致生成的摘要和第三方预期的不一致,直接认证失败。这也是很多人踩坑的点!

解决办法:自定义用户名安全令牌处理程序

写一个自定义的UserNameSecurityTokenHandler,重写密码摘要的生成逻辑,按照第三方要求的顺序拼接:

public class CustomUserNameDigestHandler : UserNameSecurityTokenHandler
{
    protected override byte[] GeneratePasswordDigest(byte[] nonce, DateTime created, string password)
    {
        // 1. 将created转换为第三方要求的ISO8601 UTC格式(比如2021-01-27T12:04:33.664Z)
        string createdStr = created.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
        // 2. 按顺序拼接:nonce字节 + created字符串字节 + 密码字符串字节(UTF-8编码)
        byte[] createdBytes = Encoding.UTF8.GetBytes(createdStr);
        byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
        
        byte[] combinedBytes = new byte[nonce.Length + createdBytes.Length + passwordBytes.Length];
        Buffer.BlockCopy(nonce, 0, combinedBytes, 0, nonce.Length);
        Buffer.BlockCopy(createdBytes, 0, combinedBytes, nonce.Length, createdBytes.Length);
        Buffer.BlockCopy(passwordBytes, 0, combinedBytes, nonce.Length + createdBytes.Length, passwordBytes.Length);
        
        // 3. 计算SHA1哈希并返回
        using (SHA1 sha1 = SHA1.Create())
        {
            return sha1.ComputeHash(combinedBytes);
        }
    }
}

然后在客户端配置中启用这个自定义处理程序:

var client = new PartnersInteraction.partnersPortTypeClient(_binding, new EndpointAddress("https://urlhere.com"));
// 添加自定义令牌处理程序
client.ClientCredentials.UserName.CustomUserNameSecurityTokenHandler = new CustomUserNameDigestHandler();
// 设置用户名和明文密码(WCF会用自定义逻辑生成摘要)
client.ClientCredentials.UserName.UserName = UserName;
client.ClientCredentials.UserName.Password = Password;

// 保留日志行为,方便排查
client.Endpoint.EndpointBehaviors.Add(_soapLoggingBehavior);

return await client.getUPIDAsync(UserName);

额外排查建议:对比SoapUI和WCF发送的请求

如果按照上面的配置还是失败,建议启用WCF的消息日志,把WCF发送的SOAP请求和SoapUI发送的请求做对比,重点检查:

  • wsse:Security节点的命名空间是否和第三方要求一致
  • Nonce的Base64值、Created的时间格式
  • PasswordDigest的Base64值是否和SoapUI生成的一致

通过对比就能快速定位到格式差异的地方。


备注:内容来源于stack exchange,提问作者svstnv

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 13:17:36