C#与SOAP UI中SOAP Header认证:读取请求用户名密码
解决SOAP Header中WSSE用户名密码读取问题
正在将SOAP服务集成到C#应用中,实现SOAP Header认证时需要从请求的WSSE Security头中读取用户名和密码,使用SOAP UI测试,请求结构如下:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/" xmlns:onl="http://schemas.datacontract.org/2004/07/OnlineDataExchange"> <soapenv:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> <wsse:UsernameToken> <wsse:Username>SOAP_ODE</wsse:Username> <wsse:Password>123456</wsse:Password> </wsse:UsernameToken> </wsse:Security> </soapenv:Header> <soapenv:Body> <tem:SendPin> <tem:request> <onl:TransactionID>?</onl:TransactionID> <onl:MSISDN>?</onl:MSISDN> </tem:request> </tem:SendPin> </soapenv:Body> </soapenv:Envelope>
现有代码文件如下:
SendPinRequest.cs
using System.Runtime.Serialization; namespace OnlineDataExchange { [DataContract] public class SendPinRequest { [DataMember(Order = 1, IsRequired = true)] public string TransactionID { get; set; } [DataMember(Order = 2, IsRequired = true)] public string MSISDN { get; set; } } }
IPinService.cs
using System.ServiceModel; using System.Threading.Tasks; namespace OnlineDataExchange.Contracts { [ServiceContract] public interface IPinService { [OperationContract] Task<SendPinResponse> SendPin(SendPinRequest request); } }
PinService.cs
using OnlineDataExchange.Contracts; using SoapCore; namespace OnlineDataExchange.Services { public class PinService : IPinService { private readonly IConfiguration _configuration; public PinService(IConfiguration configuration) { _configuration = configuration; } public async Task<SendPinResponse> SendPin(SendPinRequest request) { string resp_code = "00"; ???? // 此处需要从SOAP Header读取用户名和密码 if (request == null) throw new ArgumentNullException(nameof(request)); // 验证MSISDN if (resp_code == "00") resp_code = ValidationUtils.ValidateMSISDN(request.MSISDN); // 验证密码格式 if (resp_code == "00") resp_code = ValidationUtils.ValidatePassword(password); // 验证用户凭证 Authentication au = new Authentication(_configuration); if (resp_code == "00") resp_code = au.AuthenticateUser(username, password); return new SendPinResponse { TransactionID = request.TransactionID, ResponseCode = resp_code, ResponseText = ResponseCodes.ResponsesDictionary[resp_code], MSISDN = request.MSISDN }; } } }
可行解决方法
步骤1:定义WSSE Security数据契约类
创建对应SOAP Header中WSSE结构的数据契约类,必须保证命名空间与请求中的wsse命名空间完全一致:
using System.Runtime.Serialization; namespace OnlineDataExchange { [DataContract(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")] public class Security { [DataMember(Name = "UsernameToken")] public UsernameToken UsernameToken { get; set; } } [DataContract(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")] public class UsernameToken { [DataMember(Name = "Username")] public string Username { get; set; } [DataMember(Name = "Password")] public string Password { get; set; } } }
步骤2:通过SoapHeaderAttribute直接绑定Header(推荐)
修改服务接口,添加SoapHeader属性,将Security头直接映射为方法参数,无需手动解析:
using System.ServiceModel; using System.Threading.Tasks; using SoapCore; namespace OnlineDataExchange.Contracts { [ServiceContract] public interface IPinService { [OperationContract] [SoapHeader("Security")] Task<SendPinResponse> SendPin(SendPinRequest request, Security security); } }
更新服务实现类,直接通过参数获取用户名和密码:
using OnlineDataExchange.Contracts; using SoapCore; namespace OnlineDataExchange.Services { public class PinService : IPinService { private readonly IConfiguration _configuration; public PinService(IConfiguration configuration) { _configuration = configuration; } public async Task<SendPinResponse> SendPin(SendPinRequest request, Security security) { string resp_code = "00"; string username = security?.UsernameToken?.Username; string password = security?.UsernameToken?.Password; if (request == null) throw new ArgumentNullException(nameof(request)); // 校验用户名密码是否存在 if (resp_code == "00" && (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))) { resp_code = "401"; // 替换为你的未授权响应码 } // 验证MSISDN if (resp_code == "00") resp_code = ValidationUtils.ValidateMSISDN(request.MSISDN); // 验证密码格式 if (resp_code == "00") resp_code = ValidationUtils.ValidatePassword(password); // 验证用户凭证 Authentication au = new Authentication(_configuration); if (resp_code == "00") resp_code = au.AuthenticateUser(username, password); return new SendPinResponse { TransactionID = request.TransactionID, ResponseCode = resp_code, ResponseText = ResponseCodes.ResponsesDictionary[resp_code], MSISDN = request.MSISDN }; } } }
备选方案:手动读取并反序列化Header
如果不想修改接口参数,可通过OperationContext手动查找并解析Header:
using OnlineDataExchange.Contracts; using SoapCore; using System.ServiceModel; using System.Runtime.Serialization; using System.Xml; namespace OnlineDataExchange.Services { public class PinService : IPinService { private readonly IConfiguration _configuration; public PinService(IConfiguration configuration) { _configuration = configuration; } public async Task<SendPinResponse> SendPin(SendPinRequest request) { string resp_code = "00"; string username = null; string password = null; // 查找Security Header的索引 var securityHeaderIndex = OperationContext.Current.IncomingMessageHeaders.FindHeader( "Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" ); if (securityHeaderIndex != -1) { // 读取并反序列化Header内容 XmlReader reader = OperationContext.Current.IncomingMessageHeaders.GetReaderAtHeader(securityHeaderIndex); DataContractSerializer serializer = new DataContractSerializer(typeof(Security)); Security security = (Security)serializer.ReadObject(reader); username = security.UsernameToken?.Username; password = security.UsernameToken?.Password; } if (request == null) throw new ArgumentNullException(nameof(request)); // 校验用户名密码是否存在 if (resp_code == "00" && (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))) { resp_code = "401"; // 替换为你的未授权响应码 } // 验证MSISDN if (resp_code == "00") resp_code = ValidationUtils.ValidateMSISDN(request.MSISDN); // 验证密码格式 if (resp_code == "00") resp_code = ValidationUtils.ValidatePassword(password); // 验证用户凭证 Authentication au = new Authentication(_configuration); if (resp_code == "00") resp_code = au.AuthenticateUser(username, password); return new SendPinResponse { TransactionID = request.TransactionID, ResponseCode = resp_code, ResponseText = ResponseCodes.ResponsesDictionary[resp_code], MSISDN = request.MSISDN }; } } }
注意事项
- 必须保证
Security和UsernameToken类的命名空间与SOAP请求中的wsse命名空间完全匹配,否则无法正确解析。 - SOAP UI测试时要确保请求中包含完整的
wsse命名空间声明,避免因命名空间缺失导致解析失败。
内容的提问来源于stack exchange,提问作者Remy Sader
相关产品推荐
相关产品推荐

