【问题标题】:MVC web app (https) calling a WCF Service (https) that also requires a SSL certificate for authenticationMVC Web 应用程序 (https) 调用 WCF 服务 (https),​​该服务也需要 SSL 证书进行身份验证
【发布时间】:2012-01-08 16:48:22
【问题描述】:

我在使用 HTTPS 获取 MVC Web 应用程序以调用也使用 HTTPS 的 WCF 服务时遇到问题。 MVC Web 应用程序必须使用 WCF 服务的证书进行身份验证。

我正在使用带有 C# 和 .NET Framework 4.0 的 Windows 7、IIS 7.5、VS 2010。

这是我到目前为止所做的:

  1. 我创建了一个名为“开发机构”的证书颁发机构:

    makecert -pe -n "CN=Development Authority" -ss my -sr LocalMachine -a sha1 -sky signature -r "c:\Development Authority.cer"
    

    我使用 MMC 工具将新创建的证书导入到“受信任的根证书颁发机构”文件夹和“第三方根证书颁发机构”文件夹(本地计算机)中。此证书也可以在“个人”文件夹中找到。

    我看到如果我只在“受信任的根 CA”中导入 CA,它仍然会给出“不受信任”的警告。在“第三方根 CA”文件夹中导入 CA 后,警告消失了。

  2. 我使用以下命令创建了另外两个证书(由新创建的 CA 签名):

    makecert -pe -n "CN=mvc.localhost" -ss my -sr LocalMachine -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -in "Development Authority" -is MY -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 c:\mvc.localhost.cer
    

    通过这种方式,我创建了“mvc.localhost”和“wcf.localhost”证书。我使用 MMC 工具将它们导入到“个人”文件夹(用于本地计算机)。

  3. 在我的 Windows 主机文件中,我为将要使用 IIS 托管的两个应用程序(MVC 应用程序和 WCF 服务应用程序)创建了两个别名。我用以下几行更新了我的主机文件:

    #development
    127.0.0.1 mvc.localhost
    127.0.0.1 wcf.localhost 
    
  4. 在 IIS 下,我将一个新网站“mvc.localhost”映射到一个非常简单的 MVC Web 应用程序。 我还将一个新网站“wcf.localhost”映射到一个新创建的基本 WCF 服务。

    mcv.localhost:

    • 应用程序池: .NET Framework 4.0 使用“ApplicationPoolIdentity”运行。

    • 匿名身份验证:启用并设置为“特定用户”:“IUSR”。

    • 绑定:

      HTTP (hostname 'mvc.localhost', port 80) -> 可以通过 'http://mvc.localhost/' 访问

      HTTPS(主机名 'mvc.localhost',端口 1234)-> 可以使用 'https://mvc.localhost:1234/' 访问。添加了“mvc.localhost”证书。

    wcf.localhost:

    • 应用程序池: .NET Framework 4.0 使用“ApplicationPoolIdentity”运行。

    • 匿名身份验证:启用并设置为“特定用户”:“IUSR”。

    • 绑定:

      HTTP (hostname 'wcf.localhost', port 80) -> 可以通过 'http://wcf.localhost/' 访问

      HTTPS(主机名 'wcf.localhost',端口 443)-> 可以使用 'https://wcf.localhost/' 访问。添加了“wcf.localhost”证书。

    要修改 HTTPS 绑定的主机名,我使用了以下命令:

    c:\Windows\System32\inetsrv>appcmd set site /site.name:"wcf.localhost" /bindings.[protocol='https',bindingInformation='*:443:'].bindingInformation:*:443:wcf.localhost
    
  5. 我使用 HTTPS 分别测试了这两个网站,它们都可以正常运行(Firefox 除外,当我为测试目的创建本地证书时,它有严格的政策,并给你一个“sec_error_unknown_issuer”警告,但这不是我现在的问题)。

  6. WCF 服务配置用于证书身份验证:

    <bindings> 
      <wsHttpBinding>
        <binding name="wsHttpEndpointBinding">
          <security mode="Transport">
            <transport clientCredentialType="Certificate"/>
          </security>
        </binding>
      </wsHttpBinding> 
    </bindings>
    
    <services>
      <service name="wcf.localhost.WelcomeService"  behaviorConfiguration="wcf.localhost.WelcomeServiceBehavior">    
        <endpoint address="https://wcf.localhost/WelcomeService.svc" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" contract="wcf.localhost.IWelcomeService"> 
        </endpoint>
        <!--<endpoint address="mex" binding="mexHttpsBinding" 
        name="mexEndpoint" contract="IMetadataExchange"/>-->
      </service>
    </services>
    
    <behaviors>
      <serviceBehaviors>
        <behavior name="wcf.localhost.WelcomeServiceBehavior">
          <serviceMetadata httpsGetEnabled="true" httpGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
    
          <serviceCredentials>
            <clientCertificate>
              <authentication revocationMode="NoCheck" certificateValidationMode="PeerOrChainTrust" />
            </clientCertificate>
            <serviceCertificate findValue="<<wcf.localhost Certificate Thumbprint inserted here>>" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    

    在 IIS 中,对于 SSL 设置,我使用“要求”选项设置“要求 SSL”。

    WelcomeService 服务公开了一个名为“SayHello(string)”的方法,该方法将“Hello”连接到输入参数并返回新字符串。

    我可以毫无问题地访问“https://wcf.localhost/welcomeservice.svc”。

  7. MVC 网络应用配置:

    <bindings>
      <wsHttpBinding>
        <binding name="WSHttpBinding_IWelcomeService" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
          maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text"
          textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <reliableSession ordered="true" inactivityTimeout="00:10:00"
            enabled="false" />
          <security mode="Transport">
            <transport clientCredentialType="Certificate"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    
    <client>
      <endpoint address="https://wcf.localhost/WelcomeService.svc"
        binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IWelcomeService"
        contract="WelcomeService.IWelcomeService" name="WSHttpBinding_IWelcomeService" behaviorConfiguration="CustomBehavior">        
      </endpoint>
    </client>
    
    <behaviors>
      <endpointBehaviors>
        <behavior name="CustomBehavior" >
          <clientCredentials>
            <clientCertificate findValue="<<mvc.localhost Certificate Thumbprint inserted here>>"
            storeLocation="LocalMachine"
            storeName="My"
            x509FindType="FindByThumbprint"/>
            <serviceCertificate>
              <authentication certificateValidationMode="PeerOrChainTrust" revocationMode="NoCheck"></authentication>
            </serviceCertificate>
          </clientCredentials>          
        </behavior>
      </endpointBehaviors>
    </behaviors>
    

  8. 现在解决问题: 使用 MVC 应用程序调用 WCF 服务后,出现以下错误:

    说明:在执行当前 Web 请求期间发生未处理的异常。请查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息。

    Exception Details: System.Net.WebException: The remote server returned an error: (403) Forbidden.
    
    Source Error: 
    
    
    Line 48:         
    Line 49:         public void SayHello(string name) {
    Line 50:             base.Channel.SayHello(name);
    Line 51:         }
    Line 52:     }
    
    Source File: C:\Projects\ssl.research\mvc.localhost\Service References\WelcomeService\Reference.cs    Line: 50 
    
    Stack Trace: 
    
    
    [WebException: The remote server returned an error: (403) Forbidden.]
       System.Net.HttpWebRequest.GetResponse() +7859156
       System.ServiceModel.Channels.HttpChannelRequest.WaitForReply(TimeSpan timeout) +99
    
    [MessageSecurityException: The HTTP request was forbidden with client authentication scheme 'Anonymous'.]
       System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) +4727747
       System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) +1725
       mvc.localhost.WelcomeService.IWelcomeService.SayHello(String name) +0
       mvc.localhost.WelcomeService.WelcomeServiceClient.SayHello(String name) in C:\Projects\ssl.research\mvc.localhost\Service References\WelcomeService\Reference.cs:50
       mvc.localhost.Controllers.HomeController.CallService(HomeModel model) in C:\Projects\ssl.research\mvc.localhost\Controllers\HomeController.cs:33
       lambda_method(Closure , ControllerBase , Object[] ) +127
       System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) +264
       System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +39
       System.Web.Mvc.<>c__DisplayClass15.<InvokeActionMethodWithFilters>b__12() +129
       System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation) +826410
       System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodWithFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor, IDictionary`2 parameters) +314
       System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +825632
       System.Web.Mvc.Controller.ExecuteCore() +159
       System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +335
       System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +62
       System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +20
       System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +54
       System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +469
       System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +375
    
    Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.237
    

在客户端我也试过:

  • ServicePointManager.ServerCertificateValidationCallback += customXertificateValidation
    ...
    私有静态布尔 customXertificateValidation(对象发件人,X509Certificate 证书,
    X509Chain 链,SslPolicyErrors 错误)
        {
            返回真;
        }
  • 使用服务证书指纹对服务进行身份验证。
  • 不同的 certificateValidationMode 值。

没有任何效果。

我使用 SSL Diagnostics 1.1 来验证这两个 Web 应用程序及其证书。 没有显示错误。 SSL 握手模拟运行良好,没有错误。

我觉得我已经非常接近完成测试项目(在此过程中解决了其他问题)。

在网上搜索时,我只能找到 HTTP WCF 服务或未使用证书进行身份验证的 WCF 服务或使用证书身份验证但为 HTTP 的 WCF 服务。

我正在尝试让一个运行 HTTPS 的 MVC Web 应用调用一个运行 HTTPS 的 WCF 服务,该服务需要证书进行身份验证。

那里的许多优秀文章帮助我解决了这个项目的其他问题。

现在我被困住了,我似乎找不到解决方案。

感谢任何建议。

谢谢你,博格丹。

更新

我注意到我的两个证书都有其用途: “确保远程计算机的身份”(serverAuth),但我没有看到任何关于 clientAuth 的信息。

此外,当我访问 MVC Web 应用程序时,Chrome 浏览器会提示我选择证书。在证书列表中,我看不到我创建的两个证书。

我只能看到一个证书作为其用途之一(但与我计划使用的证书不同!): '向远程计算机证明你的身份'

也许这就是我收到错误的原因?也许我不能使用我新创建的证书来向服务器验证客户端?

有什么想法吗?如果我是对的,如何将 clientAuth 目的添加到我的任何证书中? 谢谢

【问题讨论】:

  • 您好,当您尝试从浏览器访问服务时,是否提示您输入证书?如果是这样,您作为客户证书提供的证书是什么。
  • @rauts:你是对的。它确实要求我提供证书。这有两个问题: 1. 我看不到我新创建的两个证书。 2. 如果我将客户端配置为已经使用某个证书(带有指纹..),为什么它要求我提供证书?很奇怪。

标签: wcf https iis-7.5 wcf-binding wcf-security


【解决方案1】:

与 Rajesh 一起,我可以找到解决方案。

问题在于使用“-eku”属性生成的客户端证书(mvc.localhost),该属性赋予证书“确保远程计算机的身份”(serverAuth)的目的,但不是“所需的目的”客户端认证”。

您可以在其详细信息的“常规”选项卡上验证证书用途。

因此,我必须生成一个新的客户端证书,消除“-eku”属性,该属性赋予证书“所有应用程序策略”目的(包括客户端身份验证)。

我使用以下命令完成此操作:

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC>makecert -sr LocalMachine
 -ss my  -a sha1 -n "CN=mvc.localhost" -sky exchange -pe -in "Development Author
ity" -is MY -ir LocalMachine -sp "Microsoft RSA SChannel Cryptographic Provider"
 -sy 12 c:\mvc.localhost.cer
Succeeded

小心! 使用 makecert 工具生成证书后,它们将出现在“本地机器/个人”存储中。每当您必须在不同的存储中复制/导入某个证书时,请务必先将原始证书(“本地机器/个人”存储)导出为 .pfx 文件。 然后你可以在其他商店导入.pfx文件而不是.cer文件!!

在此之后,只需将两个证书添加到正确的存储区:

本地机器 --> 个人存储 --> 有两个证书(使用 makecert 工具创建证书时自动添加)。 IIS 从此存储中选择证书(以便在创建 https 绑定时使用一个),因此我不建议删除客户端证书。

本地机器 --> 受信任的人 --> 导入的 mvc.localhost.pfx

当前用户 --> 个人商店 --> 导入 mvc.localhost.pfx(这也导入了这个文件夹中的 mvc.localhost CA)

当前用户 --> 受信任的根证书颁发机构 --> 导入的 wcf.localhost.pfx

谢谢拉杰什。

PS:你也可以在这里查看解决方案:WCF Forum Thread

【讨论】:

    【解决方案2】:

    使用证书进行身份验证时,您需要确保将证书放置在适当的存储中。

    您需要有如下客户端证书:

    在客户端机器上:

    当前用户 --> 个人文件夹应安装客户端证书 mvc.localhost.pfx

    在服务器机器上:

    本地机器 --> TrusterPeople 应该安装了 mvc.localhost.cer

    403 禁止错误是因为服务器机器无法使用它收到的证书验证客户端,因为它可能不在其受信任的存储中。


    如果客户端和服务器是同一台机器,请确保按照所述安装证书。在您的情况下,您需要拥有以下证书:

    本地机 --> 个人存储需要有服务器证书(w​​cf.localhost)

    本地机器 --> 受信任的人存储拥有客户端证书 (mvc.localhost)

    当前用户 --> 拥有客户端证书的个人存储 (mvc.localhost)

    当前用户 --> 受信任的根证书颁发机构存储具有服务器证书 (wcf.localhost)

    如果您有关注,请尝试从 IE 浏览到该服务,您应该能够看到该服务及其 wsdl。

    也去IE,然后工具--> Internet选项-->安全--> Internet-->自定义级别

    现在向下滚动到“杂项”部分,找到“当不存在证书或仅存在一个证书时不提示客户端证书选择”选项以禁用。

    现在重新启动 IE 并浏览到服务,IE 应该会要求您从个人存储中选择一个客户端证书,您需要选择 mvc.localhost。

    如果 mvc.localhost 不可见,则您的客户端证书不在适当的存储中。

    【讨论】:

    • 感谢您的回答。就我而言,客户端机器=服务器机器=本地主机。我将客户端和服务配置为使用 storeLocation = LocalMachine(这是 MMC 工具中的本地计算机)。即便如此,我在“个人”和“受信任的人”文件夹中的两个位置(LocalMachine 和 CurrentUser)都添加了两个证书(mvc.localhost 和 wcf.localhost)。它仍然不起作用。顺便说一句,两个证书都有自己的密钥,它们是使用 .pfx 文件导入的。有什么想法吗?谢谢!
    • 列表中唯一可见的证书(在 IE 提示符中)是具有“clientAuth”-->“向远程计算机证明您的身份”目的的不同证书。我的两个证书的唯一目的是“serverAuth”->“确保远程计算机的身份”(两者都是!)。我用这个问题更新了我的问题。也许这就是原因?有什么想法吗?谢谢!
    • 我在两个存储位置(本地计算机、当前用户)中为所有 3 个文件夹(个人、受信任的根 CA、受信任的人)添加了两个证书。还是不行。
    • 如果我能让我的证书出现在 IE 提示符中就好了。我不知道我做错了什么。您能否确认证书目的(正如我在上述评论或原始问题的更新中所述)不应该是问题的原因?再次感谢您的时间和兴趣。
    • 如果您的客户端证书在 IE 提示期间不可见,则该证书不在当前用户个人文件夹中。导入/安装证书时,您需要使用 pfx 文件而不是 .cer 文件。您能否从当前用户-> 个人存储中删除客户端证书 (mvc.localhost),然后使用 mvc.localhost.pfx 再次将其导入其中,然后在提示期间检查您的 IE 浏览器。在安装过程中使用 .pfx 或 .cer 确实会产生影响
    【解决方案3】:

    除了 Bogdan 的好答案(因为我有同样的问题),我将 OID_PKIX_KP_CLIENT_AUTH OID (1.3.6.1.5.5.7.3.2) 添加到 -eku 参数中,这给了我两个目的,这可能是你想要的,而不是蛮力 “所有应用程序策略”。

    例如(删除明显的换行符)

    makecert -iv SignRoot.pvk -ic signroot.cer -cy end -pe -n CN="DEV dual purpose" 
             -eku 1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2 -ss my 
             -sr localmachine -sky exchange 
             -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12    
    

    【讨论】:

      猜你喜欢
      • 2013-07-05
      • 2011-01-15
      • 1970-01-01
      • 2011-01-14
      • 1970-01-01
      • 2010-11-08
      • 2012-07-28
      • 2012-04-10
      • 2011-02-12
      相关资源
      最近更新 更多