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

如何修改WCF SOAP请求Security元素中Timestamp的命名空间前缀

解决方案:修改WCF请求中Timestamp的命名空间前缀为wsu:

针对你遇到的WCF生成的Timestamp前缀u:不被接收方认可,导致签名Timestamp校验失败的问题,以下是几个可行的修改方案:

方案一:自定义MessageEncoder修改XML内容

MessageEncoder是WCF消息序列化的核心组件,可以在消息写入流之前直接修改XML结构,包括Security头部的Timestamp前缀。

  1. 自定义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);
    }
}
  1. 创建对应的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;
}
  1. 在自定义绑定中替换默认的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。

  1. 自定义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);
        }
    }
}
  1. 在SecurityBindingElement中替换默认的SecurityTokenSerializer:
var securityElement = SecurityBindingElement.CreateCertificateOverTransportBindingElement();
securityElement.IncludeTimestamp = true;
// 替换自定义的SecurityTokenSerializer
securityElement.SecurityTokenSerializer = new WsuTimestampSecurityTokenSerializer(securityElement.SecurityVersion);

方案三:调整ClientMessageInspector的插入时机

之前你尝试的ClientMessageInspector无法访问Security头部,是因为默认的Inspector插入位置在SecurityBindingElement之后,此时消息已经被签名加密。需要将Inspector插入到SecurityBindingElement之前,确保在签名前修改消息:

  1. 自定义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)
    {
    }
}
  1. 实现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)
    {
    }
}
  1. 将自定义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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 06:15:36