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

.NET Core迁移时移除UsernameToken的XML声明解决身份断言失败

.NET Core迁移中WSDL服务身份验证错误:移除XML声明的解决方案

将旧项目从.NET Framework 4.8迁移到.NET Core 5.0时,在.NET Standard 2.0项目中对接WSDL服务,自定义SecurityWS消息头类后出现**"Failed to assert identity with UsernameToken"**错误,推测原因是消息头中包含了xml version="1.0" encoding="utf-16"声明,需要移除该XML声明以解决身份验证问题。


相关代码实现

自定义SecurityWS消息头类

public class SecurityWS : MessageHeader
{
    private readonly string _username;
    private readonly string _nonce;
    private readonly string _created;
    private readonly string _password;

    public override string Name { get; } = "Security";

    public override string Namespace { get; } =
        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";


    private string GenerateNonce()
    {
        var rng = new RNGCryptoServiceProvider();
        byte[] nonceBytes = new byte[16];
        rng.GetNonZeroBytes(nonceBytes);

        return Convert.ToBase64String(nonceBytes);
    }

    private string GenerateUTCDate()
    {
        return DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
    }

    public SecurityWS(string username, string password)
    {
        _username = username;
        _nonce = GenerateNonce();
        _created = GenerateUTCDate();
        _password = password;
    }

    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
    {
        var serializer = new XmlSerializer(typeof(UsernameToken));

        var namespaces = new XmlSerializerNamespaces();
        namespaces.Add("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        namespaces.Add("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

        PasswordWSSecurity passwordWs = new PasswordWSSecurity
        {
            Type =
                    "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
            //Type #PasswrodDigest mean PasswordOption.SendHashed in Microsoft.Web.Services3.Security.Tokens
        };
        passwordWs.Value = passwordWs.GetValuePasswordDigestStandard(_password, _nonce, _created);

        var sb = new StringBuilder();
        using (var stringWriter = new StringWriter(sb))
        using (var xmlWriter = XmlWriter.Create(stringWriter))
        {
            serializer.Serialize(xmlWriter,
            new UsernameToken
            {
                Id = $"SecurityToken-{Guid.NewGuid()}",
                Username = _username,
                Password = passwordWs,
                Nonce = _nonce,
                Created = _created
            },
            namespaces);
        }

        string xml = sb.ToString();
        int index = xml.IndexOf("?>");
        string xmlWithoutDeclaration;
        if (index != -1)
        {
            xmlWithoutDeclaration = xml.Substring(index + 2);
        }
        else
        {
            xmlWithoutDeclaration = xml;
        }
        writer.WriteRaw(xmlWithoutDeclaration);
    }
}

消息头创建逻辑

if (IsCoreProject)
{
    header = new Varyon.Engine.Services.WSSecurity.SecurityWS(user, password);
}
else
{
    UsernameToken token = new UsernameToken(user, password, PasswordOption.SendHashed);
    header = MessageHeader.CreateHeader(
        "Security",
        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
        token.GetXml(new XmlDocument())
    );
}

using (OperationContextScope scope = new OperationContextScope(service.InnerChannel))
{
    if (header != null)
        OperationContext.Current.OutgoingMessageHeaders.Add(header);
    return response = service.getUFGPartyHistory(requestHeader, ufgPartyHistory);
}

UsernameToken类定义

[XmlRoot("UsernameToken", Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public class UsernameToken
{
    [XmlAttribute(AttributeName = "Id", Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
    public string Id { get; set; }

    [XmlElement(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")] 
    public string Username { get; set; }

    [XmlElement(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
    public PasswordWSSecurity Password { get; set; }

    [XmlElement(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
    public string Nonce { get; set; }

    [XmlElement(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
    public string Created { get; set; }
}

移除XML声明的解决方案

当前代码通过字符串截取移除XML声明的方式不够可靠,推荐以下两种更稳妥的实现方式:

方案1:直接使用XmlDictionaryWriter序列化(最优)

去掉StringWriter和XmlWriter的中转逻辑,直接用传入的XmlDictionaryWriter进行序列化,不会生成XML声明:

protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
    var serializer = new XmlSerializer(typeof(UsernameToken));

    var namespaces = new XmlSerializerNamespaces();
    namespaces.Add("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
    namespaces.Add("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

    PasswordWSSecurity passwordWs = new PasswordWSSecurity
    {
        Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
    };
    passwordWs.Value = passwordWs.GetValuePasswordDigestStandard(_password, _nonce, _created);

    // 直接序列化到XmlDictionaryWriter,无XML声明
    serializer.Serialize(writer, 
        new UsernameToken
        {
            Id = $"SecurityToken-{Guid.NewGuid()}",
            Username = _username,
            Password = passwordWs,
            Nonce = _nonce,
            Created = _created
        }, 
        namespaces);
}

方案2:配置XmlWriterSettings禁用声明

如果必须保留字符串中转逻辑,可通过XmlWriterSettings明确禁用XML声明:

protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
{
    var serializer = new XmlSerializer(typeof(UsernameToken));

    var namespaces = new XmlSerializerNamespaces();
    namespaces.Add("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
    namespaces.Add("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

    PasswordWSSecurity passwordWs = new PasswordWSSecurity
    {
        Type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"
    };
    passwordWs.Value = passwordWs.GetValuePasswordDigestStandard(_password, _nonce, _created);

    var sb = new StringBuilder();
    var settings = new XmlWriterSettings
    {
        OmitXmlDeclaration = true, // 关键配置:不生成XML声明
        Encoding = Encoding.UTF8
    };

    using (var xmlWriter = XmlWriter.Create(sb, settings))
    {
        serializer.Serialize(xmlWriter,
        new UsernameToken
        {
            Id = $"SecurityToken-{Guid.NewGuid()}",
            Username = _username,
            Password = passwordWs,
            Nonce = _nonce,
            Created = _created
        },
        namespaces);
    }

    writer.WriteRaw(sb.ToString());
}

额外检查点

  • 确认Nonce的Base64编码和Created的UTC时间格式严格符合WSS规范;
  • 验证PasswordDigest生成逻辑:需将Nonce原始字节、Created字符串UTF8字节、密码字节拼接后做SHA1哈希,再转Base64编码;
  • 核对消息头的命名空间与服务端要求完全一致。

内容的提问来源于stack exchange,提问作者Mateusz Białas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 08:35:59