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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 23:02:03