C#调用SOAP API配置wsse认证报UsernameToken缺失如何解决
问题根因
你当前配置返回UsernameToken not present错误的核心原因是:BasicHttpsSecurityMode.Transport模式仅会启用HTTPS传输层的身份校验逻辑,不会在SOAP消息头中注入WS-Security规范要求的wsse:Security/UsernameToken节点,服务端解析请求时找不到对应认证头,直接返回认证失败。
解决方案
根据服务端对WSSE头格式的兼容程度,可选择以下两种方案:
方案1:调整绑定安全模式(适配标准WS-Security服务)
修改绑定的安全模式为「传输层加密+消息层凭证」,指定消息凭证类型为用户名,WCF会自动在SOAP头中生成符合规范的UsernameToken节点,保留你原来的ClientCredentials赋值逻辑即可。
BasicHttpsBinding bindingHttps = new BasicHttpsBinding(); // 关键修改:替换纯Transport模式 bindingHttps.Security.Mode = BasicHttpsSecurityMode.TransportWithMessageCredential; // 指定消息层使用用户名凭证,对应WS-Security的UsernameToken bindingHttps.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName; bindingHttps.MaxReceivedMessageSize = 500000000; bindingHttps.SendTimeout = new TimeSpan(0, 5, 0); // 原有凭证赋值逻辑保留 client.ClientCredentials.UserName.UserName = "XXXXXXXXX"; client.ClientCredentials.UserName.Password = "XXXXXXXXX";
方案2:自定义消息检查器手动注入认证头(适配格式要求严格的服务)
如果方案1配置后仍报错,说明服务端对WSSE头格式有严格要求(比如不允许默认生成的Nonce、Created节点,或指定密码明文类型),可以直接自定义客户端消息检查器,完全按照服务端给出的XML格式构造认证头注入,完全避免WCF自动生成的头格式不匹配问题。
- 实现自定义消息检查器和端点行为
using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; using System.Xml; public class WSSESecurityInspector : IClientMessageInspector { private readonly string _username; private readonly string _password; private const string WsseNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; public WSSESecurityInspector(string username, string password) { _username = username; _password = password; } public object BeforeSendRequest(ref Message request, IClientChannel channel) { XmlDocument doc = new XmlDocument(); string securityXml = $@"<wsse:Security xmlns:wsse=""{WsseNamespace}""> <wsse:UsernameToken> <wsse:Username>{_username}</wsse:Username> <wsse:Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"">{_password}</wsse:Password> </wsse:UsernameToken> </wsse:Security>"; using XmlReader reader = XmlReader.Create(new StringReader(securityXml)); XmlElement securityElement = (XmlElement)doc.ReadNode(reader); request.Headers.Add(securityElement); return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { } } public class WSSEBehavior : IEndpointBehavior { private readonly string _username; private readonly string _password; public WSSEBehavior(string username, string password) { _username = username; _password = password; } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.ClientMessageInspectors.Add(new WSSESecurityInspector(_username, _password)); } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } }
- 客户端初始化时注入自定义行为
// 初始化客户端和绑定后,添加自定义WSSE头行为 client.Endpoint.EndpointBehaviors.Add(new WSSEBehavior("你的用户名", "你的密码"));
注意事项
- 使用方案2时,不需要再设置
ClientCredentials.UserName,避免重复生成认证头 - 构造Security节点时必须保证
wsse前缀对应的命名空间和服务端要求一致,否则服务端无法识别节点 - 如果服务端要求密码为摘要类型,只需要修改Password节点的Type属性和值的生成逻辑即可
内容的提问来源于stack exchange,提问作者Lorenzo Cacciola
相关产品推荐
相关产品推荐

