如何修改WCF SOAP请求Security元素中Timestamp的命名空间前缀
解决方案:修改WCF请求中Timestamp的命名空间前缀为wsu:
针对你遇到的WCF生成的Timestamp前缀u:不被接收方认可,导致签名Timestamp校验失败的问题,以下是几个可行的修改方案:
方案一:自定义MessageEncoder修改XML内容
MessageEncoder是WCF消息序列化的核心组件,可以在消息写入流之前直接修改XML结构,包括Security头部的Timestamp前缀。
- 自定义TextMessageEncoder继承类:
public class WsuTimestampMessageEncoder : TextMessageEncoder { private readonly TextMessageEncoder _innerEncoder; private const string WsuNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; private const string OriginalPrefix = "u"; private const string TargetPrefix = "wsu"; public WsuTimestampMessageEncoder(TextMessageEncoder innerEncoder) : base(innerEncoder.MediaType, innerEncoder.MessageVersion, innerEncoder.WriteEncoding) { _innerEncoder = innerEncoder; } public override void WriteMessage(Message message, Stream stream) { // 将消息转为XML文档 var doc = new XmlDocument(); using (var ms = new MemoryStream()) { _innerEncoder.WriteMessage(message, ms); ms.Position = 0; doc.Load(ms); } // 添加wsu命名空间声明到Envelope节点 var envelopeNode = doc.DocumentElement; if (!envelopeNode.HasAttribute($"xmlns:{TargetPrefix}")) { var attr = doc.CreateAttribute($"xmlns:{TargetPrefix}"); attr.Value = WsuNamespace; envelopeNode.Attributes.Append(attr); } // 替换所有u:Timestamp为wsu:Timestamp var timestampNodes = doc.GetElementsByTagName($"{OriginalPrefix}:Timestamp"); foreach (XmlNode node in timestampNodes) { node.Prefix = TargetPrefix; } // 移除原u前缀的命名空间声明(可选) var uNamespaceAttr = envelopeNode.Attributes[$"xmlns:{OriginalPrefix}"]; if (uNamespaceAttr != null) { envelopeNode.Attributes.Remove(uNamespaceAttr); } // 将修改后的XML写入目标流 doc.Save(stream); } // 其他需要重写的方法直接委托给_innerEncoder public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType) { return _innerEncoder.ReadMessage(stream, maxSizeOfHeaders, contentType); } public override bool IsContentTypeSupported(string contentType) { return _innerEncoder.IsContentTypeSupported(contentType); } }
- 创建对应的MessageEncoderFactory:
public class WsuTimestampMessageEncoderFactory : MessageEncoderFactory { private readonly MessageEncoderFactory _innerFactory; public WsuTimestampMessageEncoderFactory(MessageEncoderFactory innerFactory) { _innerFactory = innerFactory; } public override MessageEncoder Encoder => new WsuTimestampMessageEncoder((TextMessageEncoder)_innerFactory.Encoder); public override MessageVersion MessageVersion => _innerFactory.MessageVersion; }
- 在自定义绑定中替换默认的Encoder:
public class CustomWcfBinding : CustomBinding { public CustomWcfBinding() { // 构建你的原绑定元素(包括SecurityBindingElement等) var securityElement = SecurityBindingElement.CreateCertificateOverTransportBindingElement(); securityElement.IncludeTimestamp = true; // 其他绑定元素如TextMessageEncodingBindingElement、HttpsTransportBindingElement var textEncoding = new TextMessageEncodingBindingElement(); var transport = new HttpsTransportBindingElement(); // 替换MessageEncoderFactory textEncoding.MessageEncoderFactory = new WsuTimestampMessageEncoderFactory(textEncoding.MessageEncoderFactory); this.Elements.Add(securityElement); this.Elements.Add(textEncoding); this.Elements.Add(transport); } }
方案二:自定义SecurityTokenSerializer修改Timestamp序列化前缀
Timestamp由WSSecurityTokenSerializer负责序列化,重写其WriteToken方法可以直接指定输出的前缀为wsu。
- 自定义SecurityTokenSerializer:
public class WsuTimestampSecurityTokenSerializer : WSSecurityTokenSerializer { private const string WsuPrefix = "wsu"; private const string WsuNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; public WsuTimestampSecurityTokenSerializer(SecurityVersion securityVersion) : base(securityVersion) { } protected override void WriteTokenCore(XmlWriter writer, SecurityToken token) { if (token is System.IdentityModel.Tokens.SecurityContextSecurityToken timestampToken) { // 写入wsu前缀的Timestamp元素 writer.WriteStartElement(WsuPrefix, "Timestamp", WsuNamespace); writer.WriteElementString(WsuPrefix, "Created", WsuNamespace, timestampToken.ValidFrom.ToString("o")); writer.WriteElementString(WsuPrefix, "Expires", WsuNamespace, timestampToken.ValidTo.ToString("o")); writer.WriteEndElement(); } else { // 其他类型的Token按默认逻辑处理 base.WriteTokenCore(writer, token); } } }
- 在SecurityBindingElement中替换默认的SecurityTokenSerializer:
var securityElement = SecurityBindingElement.CreateCertificateOverTransportBindingElement(); securityElement.IncludeTimestamp = true; // 替换自定义的SecurityTokenSerializer securityElement.SecurityTokenSerializer = new WsuTimestampSecurityTokenSerializer(securityElement.SecurityVersion);
方案三:调整ClientMessageInspector的插入时机
之前你尝试的ClientMessageInspector无法访问Security头部,是因为默认的Inspector插入位置在SecurityBindingElement之后,此时消息已经被签名加密。需要将Inspector插入到SecurityBindingElement之前,确保在签名前修改消息:
- 自定义BindingElement实现IClientMessageInspector的插入:
public class TimestampPrefixInspectorBindingElement : BindingElement { private readonly IClientMessageInspector _inspector; public TimestampPrefixInspectorBindingElement(IClientMessageInspector inspector) { _inspector = inspector; } public override BindingElement Clone() { return new TimestampPrefixInspectorBindingElement(_inspector); } public override T GetProperty<T>(BindingContext context) { return context.GetInnerProperty<T>(); } public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context) { context.Behaviors.Add(new TimestampPrefixInspectorBehavior(_inspector)); return base.BuildChannelFactory<TChannel>(context); } } public class TimestampPrefixInspectorBehavior : IEndpointBehavior { private readonly IClientMessageInspector _inspector; public TimestampPrefixInspectorBehavior(IClientMessageInspector inspector) { _inspector = inspector; } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { clientRuntime.ClientMessageInspectors.Add(_inspector); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } }
- 实现ClientMessageInspector修改Timestamp前缀:
public class TimestampPrefixInspector : IClientMessageInspector { public object BeforeSendRequest(ref Message request, IClientChannel channel) { // 将消息转为XML修改前缀(逻辑同方案一的XML处理部分) var doc = new XmlDocument(); using (var ms = new MemoryStream()) { request.WriteMessage(ms); ms.Position = 0; doc.Load(ms); } // 添加wsu命名空间声明并替换Timestamp前缀 var envelopeNode = doc.DocumentElement; const string wsuNs = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; if (!envelopeNode.HasAttribute("xmlns:wsu")) { var attr = doc.CreateAttribute("xmlns:wsu"); attr.Value = wsuNs; envelopeNode.Attributes.Append(attr); } var timestampNodes = doc.GetElementsByTagName("u:Timestamp"); foreach (XmlNode node in timestampNodes) { node.Prefix = "wsu"; } // 移除原u前缀声明 var uAttr = envelopeNode.Attributes["xmlns:u"]; if (uAttr != null) envelopeNode.Attributes.Remove(uAttr); // 重新创建Message using (var ms = new MemoryStream()) { doc.Save(ms); ms.Position = 0; request = Message.CreateMessage(request.Version, null, XmlReader.Create(ms)); } return null; } public void AfterReceiveReply(ref Message reply, object correlationState) { } }
- 将自定义BindingElement插入到SecurityBindingElement之前:
var customBinding = new CustomBinding(); // 先添加Inspector的BindingElement customBinding.Elements.Add(new TimestampPrefixInspectorBindingElement(new TimestampPrefixInspector())); // 再添加SecurityBindingElement和其他元素 var securityElement = SecurityBindingElement.CreateCertificateOverTransportBindingElement(); securityElement.IncludeTimestamp = true; customBinding.Elements.Add(securityElement); // 添加text encoding和transport元素 var textEncoding = new TextMessageEncodingBindingElement(); var transport = new HttpsTransportBindingElement(); customBinding.Elements.Add(textEncoding); customBinding.Elements.Add(transport);
内容的提问来源于stack exchange,提问作者Erwin
相关产品推荐
相关产品推荐

