如何在 .net core 中添加 ws-security 标头?

Ric*_*ais 6 c# wcf ws-security wcf-security asp.net-core

我正在尝试调用 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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是我调用 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);
        }
Run Code Online (Sandbox Code Playgroud)

如果我在 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>
Run Code Online (Sandbox Code Playgroud)

在 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>

Run Code Online (Sandbox Code Playgroud)

例外:

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.
Run Code Online (Sandbox Code Playgroud)

为什么我调用webservice成功后还是出现异常?

小智 6

对于.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; }
}
Run Code Online (Sandbox Code Playgroud)

在BeforeSendRequest方法中添加以下代码

public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
        var soapSecurityHeader = new SecurityHeader()
        {
            UsernameToken = new UsernameToken()
            {
                Username = "User Name"
            }
        };
        request.Headers.Add(soapSecurityHeader);
}
Run Code Online (Sandbox Code Playgroud)


321*_*21X 3

我做了一些挖掘,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);
    }
Run Code Online (Sandbox Code Playgroud)

我想在这一步中您还可以检查时间戳的值,如果DateTime.UtcNow在范围内并据此采取行动......?