如何在 .net 核心中添加 ws-security 标头?
Posted
技术标签:
【中文标题】如何在 .net 核心中添加 ws-security 标头?【英文标题】:How to add ws-security header in .net core? 【发布时间】:2019-06-08 10:52:04 【问题描述】:我正在尝试调用 web 服务并希望手动将 ws-security 标头添加到请求中,因为 .net core 2.2 目前不支持 ws-security。
我已经创建了我的自定义安全头类:
public class SoapSecurityHeader : MessageHeader
private readonly string _password, _username;
public SoapSecurityHeader(string id, string username, string password)
_password = password;
_username = username;
public override bool MustUnderstand => true;
public override string Name
get return "Security";
public override string Namespace
get return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion)
writer.WriteStartElement("wsse", Name, Namespace);
writer.WriteAttributeString("s", "mustUnderstand", "http://schemas.xmlsoap.org/soap/envelope/", "1");
writer.WriteXmlnsAttribute("wsse", Namespace);
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
writer.WriteStartElement("wsse", "UsernameToken", Namespace);
writer.WriteAttributeString("wsu", "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "UsernameToken-32");
// Username
writer.WriteStartElement("wsse", "Username", Namespace);
writer.WriteValue(_username);
writer.WriteEndElement();
// Password
writer.WriteStartElement("wsse", "Password", Namespace);
writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
writer.WriteValue(_password);
writer.WriteEndElement();
writer.WriteEndElement();
这是我调用 SOAP 服务的方法:
public ActionResult<Ted_Result> Get(DateTime dateFrom, DateTime dateTo, int? pageFrom, int? pageTo)
BasicHttpBinding basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
EndpointAddress endpointAddress = new EndpointAddress(new Uri("https://localhost/SomeService.svc"));
ChannelFactory<IConnectPublicService> factory = new ChannelFactory<IConnectPublicService>(basicHttpBinding, endpointAddress);
GetContractNoticesResponseMessage result = null;
// Bypass SSL/TLS secure channel validation
#if DEBUG
factory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
CertificateValidationMode = X509CertificateValidationMode.None,
RevocationMode = X509RevocationMode.NoCheck
;
#endif
// Debugging inspector
factory.Endpoint.EndpointBehaviors.Add(new InspectorBehavior());
IConnectPublicService serviceProxy = factory.CreateChannel();
((ICommunicationObject)serviceProxy).Open();
var opContext = new OperationContext((IClientChannel)serviceProxy);
var soapSecurityHeader = new SoapSecurityHeader("UsernameToken-32", "sampleUsername", "samplePassword123");
// Adding the security header
opContext.OutgoingMessageHeaders.Add(soapSecurityHeader);
var prevOpContext = OperationContext.Current; // Optional if there's no way this might already be set
OperationContext.Current = opContext;
var info = new ExternalIntegrationRequestMessageInfo
UserCode = "1000249",
CompanyCode = "200000040"
;
var request = new GetContractNoticesRequestMessage
Info = info,
DateFrom = dateFrom,
DateTo = dateTo,
PageFrom = pageFrom,
PageTo = pageTo
;
result = serviceProxy.GetContractNoticesAsync(request).ConfigureAwait(false).GetAwaiter().GetResult();
return Ok(result);
如果我在 BeforeSendRequest 的检查器中放置一个断点,我可以看到安全标头已添加到请求中:
<wsse:Security s:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-32" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Username>sampleUsername</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">samplePassword123</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
然后在 AfterReceiveReply 的检查器中放置一个断点,我得到了正确的结果,但我仍然得到一个异常。 结果:
<...>
<s:Header>
<...>
<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>2019-01-11T19:42:53.606Z</u:Created>
<u:Expires>2019-01-11T19:47:53.606Z</u:Expires>
</u:Timestamp>
</o:Security>
</s:Header>
<s:Body>
<GetContractNoticesResponseMessage>
<ContractNotices>....</ContractNotices>
</GetContractNoticesResponseMessage>
</s:Body>
例外:
An unhandled exception occurred while processing the request.
ProtocolException: The header 'Security' from the namespace 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' was not understood by the recipient of this message, causing the message to not be processed. This error typically indicates that the sender of this message has enabled a communication protocol that the receiver cannot process. Please ensure that the configuration of the client's binding is consistent with the service's binding.
为什么调用webservice成功后还是会出现异常?
【问题讨论】:
如果您可以访问服务器,我建议您查看日志。如果您不这样做,我建议您创建一个完整的.NET 客户端。然后,使用断点比较你正在尝试的 .NET 核心和客户端中的区别是什么 您找到解决方案了吗?我遇到了同样的问题,我可以删除AfterReceiveReply
中回复消息的标题,但这听起来像是一个糟糕的计划:-)
@321X 尚未找到解决方案。我认为解决方法是在不影响安全性的情况下修改回复的安全标头,删除标头不是一个好主意。
有人找到解决方案了吗? :) 有同样的问题。
【参考方案1】:
对于 .net core 2.2,您需要手动传递 Security 标头。您需要做一些变通方法 - WCF 尚未在 .Net Core 中完全实现(已由项目贡献者声明)。假设需求不是太复杂,您应该可以轻松完成任务。
public class SecurityHeader : MessageHeader
public UsernameToken UsernameToken get; set;
public override string Name => "Security";
public override string Namespace => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
public override bool MustUnderstand => true;
protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
XmlSerializer serializer = new XmlSerializer(typeof(UsernameToken));
serializer.Serialize(writer, this.UsernameToken);
[XmlRoot(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
public class UsernameToken
[XmlAttribute(Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd")]
public string Id get; set;
[XmlElement]
public string Username get; set;
在BeforeSendRequest方法中添加以下代码
public object BeforeSendRequest(ref Message request, IClientChannel channel)
var soapSecurityHeader = new SecurityHeader()
UsernameToken = new UsernameToken()
Username = "User Name"
;
request.Headers.Add(soapSecurityHeader);
【讨论】:
这很好用。只需要将密码添加到模型中,并且非常适合我的目的。【参考方案2】:我做了一些挖掘,在 AfterReceiveReply
你可以这样做:
public void AfterReceiveReply(ref Message reply, object correlationState)
var security = reply.Headers.Where(w => w.Namespace == "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd").First();
reply.Headers.UnderstoodHeaders.Add(security);
我想在这一步中您还可以检查时间戳的值,如果DateTime.UtcNow
在范围内并采取行动...?
【讨论】:
看来您确实必须修改回复中的安全标头。就像有人在这里做的那样:github.com/dotnet/wcf/issues/8#issuecomment-474783080 所以我假设你提出的代码是要走的路。以上是关于如何在 .net 核心中添加 ws-security 标头?的主要内容,如果未能解决你的问题,请参考以下文章
如何将 slug 添加到 asp.net 核心网站中的所有链接生成?
如何使用 .net 核心中的 SecurityTokenDescriptor 将孩子添加到 jwt 标头
如何在 vscode 中将 `System.Web.Extensions` 程序集添加到 .net 核心项目