在 WCF 中使用 Certificate 和 UserName 进行客户端身份验证,并使用 basicHttpsBinding 传递反向代理身份验证

问题描述:

我正在尝试连接到位于非标准环境中的 Web 服务.

I'm trying to connect to a Web Service, which is located in rather non-standard environment.

在带有 Web 服务的 IIS 服务器之前,我有一个反向代理服务器,它需要一个客户端证书来验证连接.之后,Web 服务本身需要额外的用户名身份验证.此外,Web Service 使用 basicHttpsBinding 和流式传输模式,因为需要发送相当大的二进制文件(最多 1GB).

Before IIS server with a Web Service I have a reverse proxy server, which requires a client certificate to authenticate the connection. After that the Web Service itself requires additional UserName authentication. Moreover Web Service uses basicHttpsBinding with streamed transfer mode, as sending of quite large binary files is required (up to 1GB).

问题是我无法让它在客户端运行.我尝试在客户端使用以下绑定安全配置:

The problem is that I can't get it working on the client side. I tried to use following binding security configuration on client side:

<security mode="TransportWithMessageCredential">
  <transport clientCredentialType="Certificate" />
  <message clientCredentialType="UserName" />
</security>

证书本身附在代码中:

client.ClientCredentials.ClientCertificate.SetCertificate(
  System.Security.Cryptography.X509Certificates.StoreLocation.CurrentUser, System.Security.Cryptography.X509Certificates.StoreName.My,
  System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");

不幸的是,我收到了 SecurityNegotiationException:无法为具有权限xxx"的 SSL/TLS 建立安全通道.当我使用 Wireshark 检查捕获的网络流量时,我可以看到,在服务器的证书请求客户端没有发送任何证书之后:

Unfortunately I got a SecurityNegotiationException: Could not establish secure channel for SSL/TLS with authority "xxx". When I checked the captured network traffic with Wireshark I could see, that after server's certificate request client didn't sent any certificate:

Transmission Control Protocol, Src Port: 8325, Dst Port: 443, Seq: 174, Ack: 3566, Len: 197
Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Multiple Handshake Messages
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 141
        Handshake Protocol: Certificate
            Handshake Type: Certificate (11)
            Length: 3
            Certificates Length: 0
        Handshake Protocol: Client Key Exchange
    TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
    TLSv1.2 Record Layer: Handshake Protocol: Encrypted Handshake Message

但是,如果我只切换到传输安全模式:

However, if I switch only to Transport security mode:

<security mode="Transport">
  <transport clientCredentialType="Certificate" />
  <message clientCredentialType="UserName" />
</security>

证书明确通过:

Transmission Control Protocol, Src Port: 8894, Dst Port: 443, Seq: 174, Ack: 3566, Len: 3215
Secure Sockets Layer
    TLSv1.2 Record Layer: Handshake Protocol: Multiple Handshake Messages
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 3159
        Handshake Protocol: Certificate
            Handshake Type: Certificate (11)
            Length: 2757
            Certificates Length: 2754
            Certificates (2754 bytes)
                Certificate Length: 1261
                Certificate: 308204e9308203d1a003020102020e78d7243f087eb6a900... (pkcs-9-at-emailAddress=xxx@domain,id-at-commonName=xxx)
                Certificate Length: 1487
                Certificate: 308205cb308203b3a003020102020e1882d07958679ac300... (id-at-commonName=xxx,id-at-organizationalUnitName=xxx,id-at-organizationName=xxx,id-at-countryName=xxx)
        Handshake Protocol: Client Key Exchange
        Handshake Protocol: Certificate Verify
    TLSv1.2 Record Layer: Change Cipher Spec Protocol: Change Cipher Spec
    TLSv1.2 Record Layer: Handshake Protocol: Encrypted Handshake Message

现在通信由反向代理转发,但被IIS拒绝,出现以下异常:

Now the communication is passed forward by reverse proxy, but is rejected by IIS with following exception:

System.ServiceModel.Security.MessageSecurityException:安全处理器无法在消息中找到安全标头.这可能是因为消息是不安全的错误,或者因为通信双方之间存在绑定不匹配.如果服务配置为安全,而客户端未使用安全,则可能会发生这种情况.

System.ServiceModel.Security.MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.

这很清楚,因为我明确关闭了消息凭据.

This is clear, since I explicitly switched off the message credentials.

不幸的是,如果 basicHttpsBinding with TransportWithMessageCredentials 安全模式支持或不支持用于传输的证书和用于消息身份验证的用户名,我没有设法找到任何准确的信息.

Unfortunately I didn't manage to find any precise information if basicHttpsBinding with TransportWithMessageCredentials security mode does, or does not support Certificate for transport and UserName for message authentication.

有人试过类似的配置吗?我发现例如以下文章 如何使用具有 SSL 传输级别安全性的基本 Http 绑定来设置 WCF 服务 但没有显示如果代理如何附加特定的客户端证书在服务器端请求一个.

Have anybody tried similar configuration? I found for instance the following article How to setup a WCF service using basic Http bindings with SSL transport level security but is doesn't show how to attach specific client certificate if a proxy on the server side requests one.

如果有任何提示,我将不胜感激.

I will be very grateful for any hint.

据我所知,BasicHttpBinding 支持用于传输的证书和用于消息身份验证的用户名.这是我之前写的一个例子,希望对你有用.
服务器(10.157.13.69,控制台应用程序)

As far as I know, BasicHttpBinding support certificate for transport and username for message authentication. Here is an example I wrote before, wish it is useful to you.
Server(10.157.13.69, Console application)

class Program
    {
        static void Main(string[] args)
        {
            Uri uri = new Uri("https://localhost:11011");
            BasicHttpBinding binding = new BasicHttpBinding();
            binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;
            binding.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

            using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
            {
                sh.AddServiceEndpoint(typeof(IService), binding, "");
                ServiceMetadataBehavior smb;
                smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
                if (smb == null)
                {
                    smb = new ServiceMetadataBehavior()
                    {
                        HttpsGetEnabled = true
                    };
                    sh.Description.Behaviors.Add(smb);
                }

                sh.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
                sh.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustUserNamePasswordVal();
                Binding mexbinding = MetadataExchangeBindings.CreateMexHttpsBinding();
                sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");

                sh.Opened += delegate
                {
                    Console.WriteLine("Service is ready");
                };
                sh.Closed += delegate
                {
                    Console.WriteLine("Service is clsoed");
                };


                sh.Open();

                Console.ReadLine();
                sh.Close();
                Console.ReadLine();
            }

        }
    }
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        string SayHello();
    }
    public class MyService : IService
    {
        public string SayHello()
        {
            return $"Hello Stranger,{DateTime.Now.ToLongTimeString()}";
        }
    }
    internal class CustUserNamePasswordVal : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (userName != "jack" || password != "123456")
            {
                throw new Exception("Username/Password is not correct");
            }
        }
    }

将证书绑定到端口.

netsh http add sslcert ipport=0.0.0.0:11011 certhash=6e48c590717cb2c61da97346d5901b260e983850 appid={ED4CE60F-6B2E-4EE6-828F-C1A6A1B12565}

客户端(通过添加服务引用调用服务)

var client = new ServiceReference1.ServiceClient();
            client.ClientCredentials.UserName.UserName = "jack";
            client.ClientCredentials.UserName.Password = "123456";
            try
            {
                var result = client.SayHello();
                Console.WriteLine(result);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

App.config(自动生成)

    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="BasicHttpBinding_IService">
                    <security mode="TransportWithMessageCredential" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="https://10.157.13.69:11011/" binding="basicHttpBinding"
                bindingConfiguration="BasicHttpBinding_IService" contract="ServiceReference1.IService"
                name="BasicHttpBinding_IService" />
        </client>
</system.serviceModel>

如果有什么我可以帮忙的,请随时与我联系.

Feel free to contact me if there is anything I can help with.