.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
相关产品推荐
相关产品推荐

