如何在C# SOAP服务引用中设置WSS-Type(PasswordText)?
解决WPF C#调用SOAP服务时的WS-Security PasswordText认证问题
看起来你混淆了HTTP Basic认证和WS-Security UsernameToken认证——SoapUI里设置的WSS-Type=PasswordText属于后者,而你当前代码添加的是HTTP层面的Basic Auth头,这就是为什么服务返回401错误:它期望的是SOAP信封内的WS-Security安全头,而非HTTP头里的Basic认证信息。
下面给你几个针对性的解决方案:
方案1:通过WCF绑定配置启用WS-Security UsernameToken
如果你是用Service Reference引入的服务,最规范的方式是修改项目的app.config/web.config,配置绑定支持WS-Security的UsernameToken认证,并指定密码类型为明文(PasswordText)。
步骤1:修改绑定配置
把默认的绑定替换为WSHttpBinding,并配置安全规则:
<system.serviceModel> <bindings> <wsHttpBinding> <binding name="MyServiceBinding"> <!-- 根据服务要求选择安全模式,如果是HTTPS可以用TransportWithMessageCredential --> <security mode="Message"> <message clientCredentialType="UserName" negotiateServiceCredential="false" establishSecurityContext="false" /> </security> </binding> </wsHttpBinding> </bindings> <client> <endpoint address="你的服务Endpoint地址" binding="wsHttpBinding" bindingConfiguration="MyServiceBinding" contract="MyServiceReference.IMyService" name="MyServiceEndpoint" /> </client> </system.serviceModel>
步骤2:简化代码逻辑
配置好绑定后,代码里只需要设置凭据,不需要手动添加HTTP头,同时要确保密码类型为明文:
using (var mainClient = new MyClient("MyServiceEndpoint")) { mainClient.ClientCredentials.UserName.UserName = "你的用户名"; mainClient.ClientCredentials.UserName.Password = "你的密码"; // 强制指定密码类型为明文(默认可能是摘要,不符合PasswordText要求) var passwordTypeProp = mainClient.ClientCredentials.UserName .GetType() .GetProperty("PasswordType", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); passwordTypeProp?.SetValue(mainClient.ClientCredentials.UserName, System.ServiceModel.Security.PasswordType.Text); var myRequestObject = ...; var mainResponse = mainClient.GetResponse(myRequestObject); }
方案2:手动添加WS-Security UsernameToken头
如果配置绑定的方式不生效(比如服务的WS-Security规则比较特殊),可以通过自定义消息检查器,完全模拟SoapUI的请求格式,手动构造WS-Security Header插入到SOAP消息中。
步骤1:创建消息检查器
public class WsSecurityUsernameTokenInspector : IClientMessageInspector { private readonly string _username; private readonly string _password; public WsSecurityUsernameTokenInspector(string username, string password) { _username = username; _password = password; } public object BeforeSendRequest(ref Message request, IClientChannel channel) { // 构造符合WS-Security标准的安全头 var securityHeader = MessageHeader.CreateHeader( "Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", GetSecurityHeaderContent(), false); request.Headers.Add(securityHeader); return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { } private XElement GetSecurityHeaderContent() { var utcNow = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); var nonce = Guid.NewGuid().ToString("N"); // 随机值,增强安全性 return new XElement( XName.Get("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"), new XElement( XName.Get("UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"), new XElement(XName.Get("Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"), _username), new XElement( XName.Get("Password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"), _password, new XAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText")), new XElement(XName.Get("Nonce", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"), Convert.ToBase64String(Encoding.UTF8.GetBytes(nonce))), new XElement(XName.Get("Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"), utcNow) ) ); } }
步骤2:创建行为扩展类
public class WsSecurityUsernameTokenBehavior : IEndpointBehavior { private readonly string _username; private readonly string _password; public WsSecurityUsernameTokenBehavior(string username, string password) { _username = username; _password = password; } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.ClientMessageInspectors.Add(new WsSecurityUsernameTokenInspector(_username, _password)); } }
步骤3:在代码中使用
using (var mainClient = new MyClient()) { // 添加WS-Security行为到客户端 mainClient.Endpoint.Behaviors.Add(new WsSecurityUsernameTokenBehavior("你的用户名", "你的密码")); var myRequestObject = ...; var mainResponse = mainClient.GetResponse(myRequestObject); }
这个方法完全复刻了SoapUI的请求格式,能精准匹配服务对WS-Security的要求。
方案3:Web Reference(ASMX)的兼容处理
如果是用Web Reference引入的服务,可以重写GetWebRequest方法来插入WS-Security Header:
public class CustomWebServiceClient : MyWebReference.MyService { protected override WebRequest GetWebRequest(Uri uri) { var request = (HttpWebRequest)base.GetWebRequest(uri); // 读取原始SOAP请求流,插入WS-Security Header后重新写入 // 具体逻辑可以参考方案2中的XML构造方式 return request; } }
不过这种方式不如Service Reference灵活,优先推荐前两种方案。
关键注意事项
- 确认服务Endpoint地址和SoapUI中使用的完全一致
- 检查服务要求的WS-Security版本(代码中用的是2004/01版本,和SoapUI默认一致)
- 如果是测试环境遇到证书验证错误,可以临时禁用验证:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
内容的提问来源于stack exchange,提问作者Shiasu-sama
相关产品推荐
相关产品推荐

