自托管WCF服务与客户端之间的双向SSL(互为SSL)出现403禁止访问错误。

4
我正在尝试在自托管的WCF服务和客户端应用程序(现阶段为命令提示符)之间建立互相SSL的演示。最终,我希望实现在使用证书进行传入连接的服务器和每个具有单独证书的客户机之间的传输安全性(而非消息安全性),以便我可以使用它们来唯一标识每个客户端。
我尝试了许多不同的方法,但都没有成功(我无法找到与我尝试做的完全相同的例子)。每次当我认为我接近成功时,我都会在调用服务时在客户端中遇到异常。我遇到的最常见的异常是:
“The HTTP request was forbidden with client authentication scheme 'Anonymous'.”
Inner exception: "The remote server returned an error: (403) Forbidden."

有没有人对我可能做错了什么或者更好的设置双向 SSL 的步骤有任何想法?
完全披露 - 我现在在同一台计算机上运行客户端和服务器。不确定这是否重要。
下面是配置片段。
服务和客户端代码相对简单,所以我相当有信心让它们正常工作。应用配置文件(特别是绑定和行为)和证书则“更有趣”,因此我对其不太自信。
以下是我如何创建证书(实际命令逐字逐句)。
makecert -pe -n "CN=SelfSignedCA" -ss Root -sr LocalMachine  -a sha1 -sky signature -r -sv "SelfSignedCA.cer" "SelfSignedCA.pvk"
makecert -pe -n "CN=system" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1  -in "SelfSignedCA" -is Root -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 Service.cer
makecert -pe -n "CN=client1" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1  -in "SelfSignedCA" -is Root -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 Client1.cer

关联证书和端口(实际命令照字面意思)
netsh http add urlacl url=https://+:44355/MyService/ user=EVERYONE

服务器设置

绑定:

  <wsHttpBinding>
    <binding name="CustomBinding">      
      <security mode="Transport">
        <transport clientCredentialType="Certificate"/>
      </security>
    </binding>
  </wsHttpBinding>

行为:

    <serviceBehaviors>
      <behavior name="">
      <!--
      <serviceCredentials>
        <serviceCertificate
           findValue="system"
           storeLocation="LocalMachine"
           storeName="My"
           x509FindType="FindBySubjectName"/>
      </serviceCredentials>
      -->
      <serviceAuthorization
         serviceAuthorizationManagerType=
              "ClientAuthorization.ClientCertificateAuthorizationManager, Simulator.Service.SideA" />
    </behavior>
  </serviceBehaviors>

客户端

绑定:

  <wsHttpBinding>
    <binding name="CustomBinding">      
      <security mode="Transport">
        <transport clientCredentialType="Certificate"/>
      </security>
    </binding>
  </wsHttpBinding>

行为

  <endpointBehaviors>
    <behavior name="ChannelManagerBehavior">
      <clientCredentials>
         <clientCertificate findValue="client1"
                           storeLocation="LocalMachine"
                           storeName="My"
                           x509FindType="FindBySubjectName" />
        <!--
        <serviceCertificate>
          <authentication certificateValidationMode="PeerOrChainTrust"/>
        </serviceCertificate>
        -->
      </clientCredentials>
     </behavior>
  </endpointBehaviors>

更新

我在服务器上添加了自定义的用户名和密码验证器,试图覆盖默认行为并始终允许无论提供的凭据如何(我真的不想进行用户名/密码验证)。这个验证器从未被调用。客户端仍然会收到“身份验证方案 '匿名'。”异常。

服务行为更新

  <serviceCredentials>
    <userNameAuthentication 
      userNamePasswordValidationMode="Custom"
      customUserNamePasswordValidatorType=
        "Service.ClientAuthorization.ClientUserNamePasswordValidatorManager, Service.SideA" />
  </serviceCredentials>
5个回答

3

这是一个关于编程的演示,以下是我的参考测试结果。我在Win7 + VS2010 + 客户端和服务器在同一台机器上进行了测试。

服务器端:

[ServiceContract(Name="CalculatorService")]
    public interface ICalculatorService {
        [OperationContract]
        int Add(int x, int y);
    }

public class CalculatorService : ICalculatorService {
        public Int32 Add(Int32 x, Int32 y) {
            Console.WriteLine("{0}: service method called (x = {1}, y = {2})",
                Thread.CurrentThread.ManagedThreadId, x, y);
            return x + y;
        }
    }

class Program {
        static void Main(string[] args) {
            ServicePointManager.ServerCertificateValidationCallback +=
                (sender, certificate, chain, sslPolicyErrors) => true;

            using (var serviceHost = new ServiceHost(typeof(CalculatorService))) {
                serviceHost.Opened += delegate {
                    Console.WriteLine("{0}: service started", 
                        Thread.CurrentThread.ManagedThreadId);
                };
                serviceHost.Open();
                Console.Read();
            }
        }
    }

<?xml version="1.0" encoding="utf-8" ?> <configuration>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="transportSecurity">
                    <security mode="Transport">
                        <transport clientCredentialType="Certificate"/>
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>

        <services>
            <service name="WcfService.CalculatorService">
                <endpoint address="https://hp-laptop:3721/calculatorservice"
                          binding="wsHttpBinding"
                          bindingConfiguration="transportSecurity"
                          contract="Contract.ICalculatorService" />
            </service>
        </services>
    </system.serviceModel> </configuration>

客户端:

class Program {
        static void Main(string[] args) {
            using (var channelFactory =
                new ChannelFactory<ICalculatorService>("calculatorservice")) {
                ICalculatorService proxy = channelFactory.CreateChannel();
                Console.WriteLine(proxy.Add(1, 2));
                Console.Read();
            }
            Console.Read();
        }
    }

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="transportSecurity">
                    <security mode="Transport">
                        <transport clientCredentialType="Certificate"/>
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <behaviors>
            <endpointBehaviors>
                <behavior name="defaultClientCertificate">
                    <clientCredentials>
                        <clientCertificate 
                            storeLocation="LocalMachine" 
                            storeName="My" 
                            x509FindType="FindBySubjectName" 
                            findValue="client1"/>
                    </clientCredentials>
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <client>
            <endpoint name="calculatorservice" behaviorConfiguration="defaultClientCertificate"
                      address="https://hp-laptop:3721/calculatorservice"
                      binding="wsHttpBinding"
                      bindingConfiguration="transportSecurity"
                      contract="Contract.ICalculatorService"/>
        </client>
    </system.serviceModel>
</configuration>

证书创建:
自行创建CA
makecert -n "CN=RootCA" -r -sv c:\rootca.pvk c:\rootca.cer 创建后,通过证书控制台将此证书导入“可信根证书颁发机构”。这是防止您提到的异常的步骤。
用于服务程序的证书
makecert -n "CN=hp-laptop" -ic c:\rootca.cer -iv c:\rootca.pvk -sr LocalMachine -ss My -pe -sky exchange 请注意,上面的CN值应与服务地址的DNS部分匹配。例如,hp-laptop是我的计算机名称。服务终结点的地址将为“ https://google.com:/...”(由于某些StackOverflow规则,请将google点com替换为' hp-laptop')。
向服务程序注册服务证书:

netsh http add sslcert ipport=0.0.0.0:3721 certhash=‎6c78ad6480d62f5f460f17f70ef9660076872326 appid={a0327398-4069-4d2d-83c0-a0d5e6cc71b5}

certhash的值是服务程序证书的指纹(在证书控制台中检查)。 appid是服务程序文件“AssemblyINfo.cs”中的GUID。

客户端程序的证书:

makecert -n "CN = client1" -ic c:\rootca.cer -iv c:\rootca.pvk -sr LocalMachine -ss My -pe -sky exchange

更新:根据typhoid对该解决方案的经验,由于服务器中有太多受信任的根机构,“匿名”异常仍然存在。 typhoid提供了两个链接来解决此问题。

http://support.microsoft.com/kb/2464556

http://blog.codit.eu/post/2013/04/03/Troubleshooting-SSL-client-certificate-issue-on-IIS.aspx


是的,我在“控制台根”->“证书”(本地计算机)->“受信任的根证书颁发机构”->“证书”下拥有“RootCA”证书。我还在“控制台根”->“证书”(本地计算机)->“个人”->“证书”下拥有客户端1和[我的机器]证书。这两个证书都能够正确连接到RootCA。 - typhoid
当我在IE中浏览到https://localhost:44444/calculatorservice/(我使用的是不同的端口)时,我会收到相同的HTTP 403错误。 - typhoid
你能详细说明一下“创建后,通过证书控制台将此证书导入到‘受信任的根证书颁发机构’中。这是防止您提到的异常的步骤。”吗?你是说要将rootca.cer导入到证书(本地计算机或当前用户)-> 受信任的根证书颁发机构 -> 证书中吗? - typhoid
昨晚我创建了一个新项目,其中包含我在家中放置在这个答案中的源代码部分。它运行良好,只是找不到哪个部分导致身份验证方案“匿名”异常。我猜问题仍然出在证书创建部分。之前我遇到过这个异常,当创建的根证书不在受信任的根CA列表中时会出现。但一旦它在那个列表中,这个异常就不会再出现了。 - Yang You
嗯...还是不行。我再次按照你上面的步骤操作了(这次我没有改变端口),但我仍然得到相同的异常。目前我看到我们设置之间唯一的区别是我使用的是VS 2012。你能否发布你的解决方案和证书文件供我使用,以尝试排除此计算机上的某些奇怪问题? - typhoid
显示剩余5条评论

3

另一个选项是监控实际交换,包括ssl v3握手。我遇到了类似的问题,最终发现SSL握手失败是因为我信任的证书颁发机构太多了。为了解决这个问题,我需要按照此解决方案进行操作。该解决方案使Windows的SSL部分不会将信任的证书颁发机构发送回客户端,从而允许握手完成。


这就是我遇到的问题。yyou已经添加了这些注释到他的解决方案中,以提供完整的解决方案。谢谢! - typhoid
谢谢分享!我今天(2018年)遇到了这个问题,你的解决方案解决了我的问题。 - Robert

2

我曾经遇到了相同的问题,几天后终于意识到这行代码创建了一个“服务器认证”证书。你需要一个“客户端认证”证书,否则客户端证书将被视为无效。

makecert -pe -n "CN=client1" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.2 -in "SelfSignedCA" -is Root -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 Client1.cer

因此,只需将-eku参数改为21.3.6.1.5.5.7.3.2


0
我遇到了同样的问题(Forbidden 403),我的客户试图使用MutualAuthentication连接服务器。确实,SSL层不是问题,同时服务器没有收到请求,因为它被传输层阻止了。 但是我只在2012服务器上遇到了这个问题,而2008则可以正常工作。 通过服务配置启用serviceSecurityAudit。
<serviceSecurityAudit auditLogLocation="Application"
            suppressAuditFailure="false" 
            serviceAuthorizationAuditLevel="SuccessOrFailure" 
            messageAuthenticationAuditLevel="SuccessOrFailure" />

在 Windows 事件日志应用程序中显示了消息认证和服务授权错误,表明出现了错误/失败。

对我有效的解决方案是通过自定义证书验证器来引入 clientCertificate。

   <clientCertificate>              <authentication certificateValidationMode="Custom" customCertificateValidatorType="App.Web.Framework.MyX509CertificateValidator, App.Vertical.Send"  />
   </clientCertificate>

这需要在程序集中实现证书验证方法。

如果需要,我也可以提供详细信息。

谢谢 --Kirti Kunal Shah


0

在任何HTTP流量通过之前,SSL握手必须成功完成。您收到任何HTTP响应的事实表明您的SSL设置正常工作。

403禁止响应表示服务器设置为在提供资源/页面之前需要HTTP基本身份验证用户名和密码。除了您已经在TLS / SSL级别执行的操作之外,您需要让客户端在HTTP级别提供基本身份验证用户名/密码,或者您需要设置服务器以允许无需HTTP基本身份验证即可访问。


我想允许匿名访问,并根据客户端提供的证书执行授权,但似乎没有办法更改服务器配置以启用匿名访问。 - typhoid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接