【问题标题】:Self-Hosted WCF Service with Mutual SSL (between Service and Client) fails with 403 Forbidden具有相互 SSL(在服务和客户端之间)的自托管 WCF 服务失败并显示 403 Forbidden
【发布时间】:2014-04-20 00:34:01
【问题描述】:

我正在尝试在 自托管 WCF 服务和客户端应用程序之间设置 Mutual SSL 演示(现在是命令提示符)。最后,我试图找到一个解决方案,在该解决方案中,我在使用证书进行传入连接的服务器和多个客户端之间具有传输安全性(不是消息安全性),每个客户端都有我可以使用的单独证书用于唯一标识每个客户端。

我已经尝试了许多不同的方法,但都没有奏效(我无法找到我一直在尝试做的事情的确切示例)。每次我认为我已经接近时,当我尝试调用服务时,我最终都会在客户端出现异常。我遇到的最常见的异常是:

“The HTTP request was forbidden with client authentication scheme 'Anonymous'.”
Inner exception: "The remote server returned an error: (403) Forbidden."

是否有人对我可能做错了什么有任何想法,或者可能更好地了解如何在上述场景中设置相互 SSL?

完全披露-截至目前,我在同一台计算机上同时运行客户端和服务器。不确定这是否重要。

在下面配置sn-ps

服务和客户端代码相对简单,所以我非常有信心让它们正常工作。应用配置(特别是绑定和行为)和证书“更有趣”,所以我对此没有那么自信。

我如何创建证书(逐字逐句的实际命令)

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>

【问题讨论】:

    标签: wcf ssl https mutual-authentication


    【解决方案1】:

    这是供您参考的演示。我在win7 + vs2010 + client-server-on-same-machine下测试过。

    服务器端:

    [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 我的 -pe -sky 交换

    请注意,上面的 CN 值应与服务地址的 DNS 部分匹配。例如hp-laptop 是我的电脑名称。服务端点的地址将是“https://google.com:/...”。 (由于某些 stackoverflow 规则,将 google dot com 替换为 'hp-laptop')。

    向服务程序注册服务证书:

    netsh http 添加 sslcert ipport=0.0.0.0:3721 certash=‎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 我的 -pe -sky 交换

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

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

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

    【讨论】:

    • 感谢您的笔记。这一切对我来说都很有意义,而且很容易理解。但是,我按照您的示例代码/步骤两次,在这两种情况下,我都看到了“身份验证方案‘匿名’”。客户端调用“proxy.Add(1, 2)”时出现异常。顺便说一句,我使用的是 Win 7 和 VS 2012
    • 您是否已将 rootCA 证书导入证书控制台(在 Windows RUN 中键入“mmc”并将证书添加到 mmc。记住选择“本地计算机”而不是当前用户)。在执行此步骤之前,我遇到了同样的错误。
    • 是的,我在控制台根目录 -> 证书(本地计算机) -> 受信任的根证书颁发机构 -> 证书下有“RootCA”证书。我在控制台根目录 -> 证书(本地计算机) -> 个人 -> 证书下也有 client1 和 [我的机器] 证书。这两个都没有错误地链接到 RootCA。
    • 刚刚注意到这个......如果你在 serviceHost.Open() 语句之后中断然后深入到 serviceHost->base->Authorization 我看到“PrincipalPermissionMode”设置为“UseWindowsGroups” .
    • 当我在 IE 中浏览到 localhost:44444/calculatorservice(我使用与您不同的端口)时,我收到相同的 HTTP 403 错误。
    【解决方案2】:

    另一个选项是monitor the actual exchange,包括 ssl v3 握手。我遇到了类似的事情,最终发现 SSL 握手失败了,因为我有太多受信任的证书颁发机构。为了解决这个问题,我需要关注this resolution。该解决方案阻止 Windows 的 SSL 部分将受信任的证书颁发机构发送回客户端;因此,允许握手完成。

    【讨论】:

    • 这是我遇到的问题。 yyou 已将这些注释添加到他的解决方案中,以提供问题的完整解决方案。谢谢!
    • 感谢分享!我今天(2018 年)遇到了这个问题,你的解决方案成功了。
    【解决方案3】:

    我几天都遇到同样的问题,直到最后,我意识到这一行创建了一个“服务器身份验证”证书。您需要“客户端身份验证”证书,否则客户端证书将被视为无效。

    makecert -pe -n "CN=client1" -ss my -sr LocalMachine -a sha1 -sky 交换 -eku 1.3.6.1.5.5.7.3.1 -in "SelfSignedCA" -is Root -ir LocalMachine -sp“Microsoft RSA SChannel 加密提供程序”-sy 12 客户端1.cer

    所以只需将 2 用于 -eku 参数:1.3.6.1.5.5.7.3.2

    【讨论】:

      【解决方案4】:

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

      403 Forbidden 响应表明服务器设置为在提供资源/页面之前需要 HTTP 基本身份验证用户名和密码。除了您已经在 TLS/SSL 级别执行的操作之外,您还需要您的客户端在 HTTP 级别提供基本身份验证用户名/密码,或者您需要设置服务器以允许在没有 HTTP 基本身份验证的情况下进行访问。

      【讨论】:

      • 我想允许匿名访问,并根据客户端提供的证书执行授权,但我似乎没有看到更改服务器配置以启用匿名访问的方法。跨度>
      【解决方案5】:

      我的客户端尝试使用 MutualAuthentication 连接到服务器时遇到了同样的问题(禁止 403)。 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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-02
        相关资源
        最近更新 更多