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

自定义WCF绑定中手动抛出的FaultException缺少WS-Security时间戳

问题:手动抛出的FaultException缺少WS-Security时间戳安全头

场景重现

我在服务端和客户端使用以下自定义UserNameWsTrustBinding:

public class UserNameWsTrustBinding : Binding
{
    public override BindingElementCollection CreateBindingElements()
    {
        var coll = new BindingElementCollection();
        coll.Add(CreateSecurityBindingElement());
        coll.Add(new TextMessageEncodingBindingElement());
        coll.Add(new HttpsTransportBindingElement());
        return coll;
    }

    private SecurityBindingElement CreateSecurityBindingElement()
    {
        var elem = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
        elem.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
        return elem;
    }
}

绑定运行正常,但在验证用户凭据时手动抛出非泛型FaultException:

throw new FaultException(new FaultReason("Blah blah"), new FaultCode("Code42"));

对应的SOAP响应缺少WS-Security时间戳安全头:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
    xmlns:a="http://www.w3.org/2005/08/addressing">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://www.w3.org/2005/08/addressing/soap/fault</a:Action>
        <a:RelatesTo>urn:uuid:ff1f54d7-53a0-4650-b967-03a75def5fa4</a:RelatesTo>
    </s:Header>
    <s:Body>
        <s:Fault>
            <s:Code>
                <s:Value>s:Sender</s:Value>
                <s:Subcode><s:Value>Code42</s:Value></s:Subcode>
            </s:Code>
            <s:Reason><s:Text>Blah blah</s:Text></s:Reason>
        </s:Fault>
    </s:Body>
</s:Envelope>

客户端收到该响应时抛出异常:

System.ServiceModel.Security.MessageSecurityException: An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.

奇怪的是,服务器中未被捕获的未处理异常会被自动包装成FaultException,这类响应会带有完整的安全头:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher/fault</a:Action>
        <a:RelatesTo>urn:uuid:0baffa8b-07ee-4feb-bc44-7e2c7ae85c22</a:RelatesTo>
        <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <u:Timestamp u:Id="_0">
                <u:Created>2022-07-11T13:41:22.007Z</u:Created>
                <u:Expires>2022-07-11T13:46:22.007Z</u:Expires>
            </u:Timestamp>
        </o:Security>
    </s:Header>
    <s:Body>
        <s:Fault>
            <s:Code>
                <s:Value>s:Receiver</s:Value>
                <s:Subcode><s:Value xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher">a:InternalServiceFault</s:Value></s:Subcode>
            </s:Code>
            <s:Reason><s:Text>The server was unable to process the request due to an internal error.  For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the &lt;serviceDebug&gt; configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework SDK documentation and inspect the server trace logs.</s:Text></s:Reason>
        </s:Fault>
    </s:Body>
</s:Envelope>

解决方案

方案1:使用泛型FaultException<T>并配置FaultContract

WCF对泛型FaultException<T>的处理会严格遵循安全绑定策略,确保响应包含必要的安全头,步骤如下:

  1. 定义自定义错误数据契约类:
[DataContract]
public class CustomCredentialFault
{
    [DataMember]
    public string Message { get; set; }
}
  1. 在服务契约接口中添加FaultContract标记:
[ServiceContract]
public interface IYourService
{
    [OperationContract]
    [FaultContract(typeof(CustomCredentialFault))]
    void YourOperation();
}
  1. 抛出泛型版本的FaultException:
var faultDetail = new CustomCredentialFault { Message = "Blah blah" };
throw new FaultException<CustomCredentialFault>(
    faultDetail,
    new FaultReason("Blah blah"),
    new FaultCode("Code42")
);

方案2:自定义消息检查器(IDispatchMessageInspector)

通过实现IDispatchMessageInspector,在发送错误响应前手动添加WS-Security时间戳头,确保所有Fault响应都包含安全头:

  1. 实现消息检查器:
public class SecurityFaultInspector : IDispatchMessageInspector
{
    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
    {
        return null;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
        if (reply.IsFault)
        {
            // 创建WS-Security时间戳头
            var timestamp = new Timestamp(DateTime.UtcNow, DateTime.UtcNow.AddMinutes(5));
            var securityHeader = new SecurityHeader();
            securityHeader.Add(timestamp);

            // 将安全头添加到响应中
            reply.Headers.Add(securityHeader);
        }
    }
}
  1. 创建自定义行为扩展:
public class SecurityFaultBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new SecurityFaultInspector());
    }

    public void Validate(ServiceEndpoint endpoint) { }
}
  1. 将行为添加到服务端点:
var host = new ServiceHost(typeof(YourService));
var endpoint = host.AddServiceEndpoint(typeof(IYourService), new UserNameWsTrustBinding(), "https://...");
endpoint.Behaviors.Add(new SecurityFaultBehavior());
host.Open();

方案3:调整服务行为配置

确保服务行为中启用IncludeExceptionDetailInFaults(仅用于调试或明确需要返回异常详情的场景),同时结合泛型FaultException使用,确保WCF的安全管道处理所有Fault响应:

[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class YourService : IYourService
{
    // 服务实现
}

内容的提问来源于stack exchange,提问作者Jan Köhler

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 08:24:10