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

