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

.NET Core 8中如何为WCF客户端添加WS-Security安全头?

.NET Core 8 为WCF客户端添加WS-Security(wsse:Security)头实现方案

一、先明确WS-Security头核心组件逻辑

你提到的几个必填项作用和计算逻辑:

  • Timestamp:防重放攻击,格式为UTC时间(yyyy-MM-ddTHH:mm:ss.fffZ),包含Created(当前时间)和Expires(通常延后5-10分钟)
  • BinarySecurityToken:你的客户端X.509证书的Base64编码,服务端用它验证签名的合法性
  • 摘要(Digest):对SOAP消息中指定节点(如Body、Timestamp)做哈希计算(常见SHA-256),确保消息未被篡改
  • 签名(Signature):用客户端证书的私钥对摘要集合做非对称加密,服务端用公钥解密验证身份和完整性

二、优先用WCF原生配置实现(推荐)

不要手动改XML,WCF自带WS-Security扩展,通过CustomBinding配置更可靠:

1. 构建支持WS-Security的自定义绑定

private static CustomBinding CreateWsSecurityBinding()
{
    // 基础HTTP传输层
    var httpTransport = new HttpTransportBindingElement();

    // 初始化WS-Security绑定元素(根据服务要求选择认证模式)
    // 如果是客户端证书签名、服务端证书验证,用CreateCertificateOverTransportBindingElement
    var securityElement = SecurityBindingElement.CreateCertificateOverTransportBindingElement(
        MessageSecurityVersion.WSSecurity11WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
    
    // 强制包含Timestamp,指定算法套件(必须和服务端一致)
    securityElement.IncludeTimestamp = true;
    securityElement.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256; // 适配SHA-256哈希/签名

    // 编码层用SOAP11+WS-Addressing1.0(匹配服务端WSDL版本)
    var textEncoding = new TextMessageEncodingBindingElement(MessageVersion.Soap11WSAddressing10, Encoding.UTF8);

    return new CustomBinding(securityElement, textEncoding, httpTransport);
}

2. 初始化客户端并配置证书

// 服务端地址
var endpointAddr = new EndpointAddress("https://第三方服务域名/Service.svc");
// 使用自定义绑定创建客户端
_client = new YourGeneratedWcfClient(CreateWsSecurityBinding(), endpointAddr);

// 加载客户端证书(用于生成BinarySecurityToken和签名)
_client.ClientCredentials.ClientCertificate.SetCertificate(
    StoreLocation.CurrentUser,
    StoreName.My,
    X509FindType.FindByThumbprint,
    "你的证书指纹");

这样配置后,WCF会自动为请求添加符合标准的wsse:Security头,包含你需要的所有组件。

三、如果原生配置不满足自定义要求(比如服务端有特殊签名规则)

用改进版IClientMessageInspector手动构建安全头,解决你之前修改XML失败的问题:

1. 实现自定义消息检查器

public class CustomWsSecurityInspector : IClientMessageInspector
{
    private readonly X509Certificate2 _clientCert;
    private readonly XmlNamespaceManager _nsManager;

    public CustomWsSecurityInspector(X509Certificate2 clientCert)
    {
        _clientCert = clientCert;
        _nsManager = new XmlNamespaceManager(new NameTable());
        _nsManager.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
        _nsManager.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
        _nsManager.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        // 为Body添加wsu:Id(签名需要引用)
        var bodyId = $"Body-{Guid.NewGuid()}";
        ModifyMessageBodyId(ref request, bodyId);

        // 创建wsse:Security头并添加到请求
        var securityXml = BuildSecurityHeaderXml(bodyId);
        var securityHeader = MessageHeader.CreateHeader(
            "Security",
            _nsManager.LookupNamespace("wsse"),
            securityXml,
            false);
        request.Headers.Add(securityHeader);

        return null;
    }

    private XmlElement BuildSecurityHeaderXml(string bodyId)
    {
        var doc = new XmlDocument();
        var securityNode = doc.CreateElement("wsse", "Security", _nsManager.LookupNamespace("wsse"));

        // 1. 添加Timestamp
        var timestampNode = AddTimestampNode(doc);
        securityNode.AppendChild(timestampNode);
        var timestampId = timestampNode.Attributes["wsu:Id"].Value;

        // 2. 添加BinarySecurityToken
        var tokenNode = AddBinarySecurityTokenNode(doc);
        securityNode.AppendChild(tokenNode);

        // 3. 生成签名(包含Timestamp和Body的摘要)
        var signatureNode = GenerateSignature(doc, timestampId, bodyId);
        securityNode.AppendChild(signatureNode);

        return securityNode;
    }

    private XmlElement AddTimestampNode(XmlDocument doc)
    {
        var timestampNode = doc.CreateElement("wsu", "Timestamp", _nsManager.LookupNamespace("wsu"));
        timestampNode.SetAttribute("wsu:Id", $"TS-{Guid.NewGuid()}");
        
        var created = doc.CreateElement("wsu", "Created", _nsManager.LookupNamespace("wsu"));
        created.InnerText = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
        var expires = doc.CreateElement("wsu", "Expires", _nsManager.LookupNamespace("wsu"));
        expires.InnerText = DateTime.UtcNow.AddMinutes(5).ToString("yyyy-MM-ddTHH:mm:ss.fffZ");
        
        timestampNode.AppendChild(created);
        timestampNode.AppendChild(expires);
        return timestampNode;
    }

    private XmlElement AddBinarySecurityTokenNode(XmlDocument doc)
    {
        var tokenNode = doc.CreateElement("wsse", "BinarySecurityToken", _nsManager.LookupNamespace("wsse"));
        tokenNode.SetAttribute("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
        tokenNode.SetAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
        tokenNode.SetAttribute("wsu:Id", $"Cert-{Guid.NewGuid()}");
        tokenNode.InnerText = Convert.ToBase64String(_clientCert.RawData);
        return tokenNode;
    }

    private XmlElement GenerateSignature(XmlDocument doc, string timestampId, string bodyId)
    {
        var signedXml = new SignedXml(doc);
        signedXml.SigningKey = _clientCert.GetRSAPrivateKey();

        // 添加Timestamp引用
        var timestampRef = new Reference($"#{timestampId}");
        timestampRef.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
        signedXml.AddReference(timestampRef);

        // 添加Body引用
        var bodyRef = new Reference($"#{bodyId}");
        bodyRef.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
        signedXml.AddReference(bodyRef);

        // 添加证书密钥信息
        var keyInfo = new KeyInfo();
        keyInfo.AddClause(new KeyInfoX509Data(_clientCert));
        signedXml.KeyInfo = keyInfo;

        // 计算签名
        signedXml.ComputeSignature();
        return signedXml.GetXml();
    }

    private void ModifyMessageBodyId(ref Message request, string bodyId)
    {
        // 将Message转换为XmlDocument,为Body添加wsu:Id属性
        var buffer = request.CreateBufferedCopy(int.MaxValue);
        var msgCopy = buffer.CreateMessage();
        var xmlDoc = new XmlDocument();
        using (var reader = msgCopy.GetReaderAtBodyContents())
        {
            xmlDoc.Load(reader);
        }
        xmlDoc.DocumentElement.SetAttribute("wsu:Id", _nsManager.LookupNamespace("wsu"), bodyId);

        // 重新构建Message
        var ms = new MemoryStream();
        xmlDoc.Save(ms);
        ms.Position = 0;
        request = Message.CreateMessage(request.Version, null, XmlReader.Create(ms));
        buffer.Close();
    }

    public void AfterReceiveReply(ref Message reply, object correlationState) { }
}

2. 注册消息检查器到客户端

// 创建检查器实例
var securityInspector = new CustomWsSecurityInspector(_clientCert);
// 添加到客户端行为
_client.Endpoint.Behaviors.Add(new EndpointBehavior { ClientMessageInspectors = { securityInspector } });

// 定义EndpointBehavior辅助类
public class EndpointBehavior : IEndpointBehavior
{
    public List<IClientMessageInspector> ClientMessageInspectors { get; } = new();

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        foreach (var inspector in ClientMessageInspectors)
        {
            clientRuntime.ClientMessageInspectors.Add(inspector);
        }
    }
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
    public void Validate(ServiceEndpoint endpoint) { }
}

四、关键验证点

  • 确认服务端要求的哈希/签名算法(比如SHA-1还是SHA-256),代码里的DigestMethod和DefaultAlgorithmSuite必须匹配
  • 客户端证书必须是服务端信任的,且包含可导出的私钥
  • Timestamp的有效期不能超出服务端允许的范围(比如有些服务只允许2分钟内的请求)
  • 签名覆盖的节点必须和服务端要求完全一致(比如是否包含自定义头)

完成以上配置后,直接调用_client.CreateAsync(MessageHeader, Partner);即可,请求会自动带上符合要求的wsse:Security头。

内容的提问来源于stack exchange,提问作者Mohammad Al-Daqqah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 11:22:08