MVC Web应用程序(https)调用WCF服务(https),该服务还需要SSL证书进行身份验证

时间:2011-11-29 10:25:42

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

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

我使用的是Windows 7,IIS 7.5,VS 2010与C#和.NET Framework 4.0。

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

  1. 我创建了一个名为' Development Authority':

    的证书颁发机构
    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”#39;和' wcf.localhost'证书。我将它们导入了“个人”中。文件夹(对于本地计算机),使用MMC工具。

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

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

    mcv.localhost:

    • 应用程序池:使用' ApplicationPoolIdentity'正在运行的.NET Framework 4.0。

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

    • 绑定:

      HTTP(主机名' mvc.localhost',端口80) - >可以使用' http://mvc.localhost/'

      访问

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

    wcf.localhost:

    • 应用程序池:使用' ApplicationPoolIdentity'正在运行的.NET Framework 4.0。

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

    • 绑定:

      HTTP(主机名' wcf.localhost',端口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. 用于证书身份验证的
  7. 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&#39;与'要求&#39;选项。

    服务WelcomeService公开了一个名为&#39; SayHello(string)&#39;的方法。那个连接'你好'&#39;输入参数并返回新字符串。

    我可以联系到https://wcf.localhost/welcomeservice.svc'没有问题。

  8. 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>
    

  9. 现在出现问题: 使用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
    
  10. 在客户端,我也尝试过:

    • ServicePointManager.ServerCertificateValidationCallback += customXertificateValidation
      ...
      private static bool customXertificateValidation(object sender, X509Certificate cert,
      X509Chain chain, SslPolicyErrors error)
          {
              return true;
          }
    • 使用服务证书指纹对服务进行身份验证。
    • 不同的certificateValidationMode值。

    没有任何效果。

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

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

    在网上搜索时,我发现只有HTTP WCF服务或WCF服务没有使用证书进行身份验证或使用证书身份验证但是HTTP的WCF服务。

    我尝试让运行MVC网络应用程序的HTTPS调用运行WCF服务的HTTPS,该服务需要证书才能进行身份验证。

    许多伟大的文章帮助我解决了这个项目的其他问题。

    现在我被卡住了,似乎无法找到解决方案。

    任何建议表示赞赏。

    谢谢你,波格丹。

    更新

    我注意到我的两张证书都有其目的: &#39;确保远程计算机的身份。 (serverAuth)但我没有看到关于clientAuth的任何信息。

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

    我只能看到一个证书作为其目的之一(但与我计划使用的证书不同!): &#39;向远程计算机证明您的身份&#39;

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

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

3 个答案:

答案 0 :(得分:3)

与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文件!!

在此之后,只需将两个证书添加到正确的商店:

本地计算机 - &gt;个人商店 - &gt;有两个证书(使用makecert工具创建证书时自动添加)。 IIS从此存储中选择证书(为了在创建https绑定时使用一个),因此我不建议删除客户端证书。

本地计算机 - &gt;值得信赖的人 - &gt;导入mvc.localhost.pfx

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

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

感谢Rajesh。

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

答案 1 :(得分:1)

使用证书进行身份验证时,您需要确保将证书放在相应的商店中。

您需要拥有以下客户证书:

在客户端计算机上:

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

在服务器计算机上:

本地计算机 - &gt; TrusterPeople应该安装mvc.localhost.cer

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


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

本地计算机 - &gt;个人存储需要具有服务器证书(w​​cf.localhost)

本地计算机 - &gt;受信任的人存储以获得客户端证书(mvc.localhost)

当前用户 - &gt;个人存储具有客户端证书(mvc.localhost)

当前用户 - &gt;受信任的根证书授权存储区具有服务器证书(w​​cf.localhost)

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

还可以转到IE,然后转到工具 - &gt;互联网选项 - &gt;安全 - &gt;互联网 - &gt;自定义级别

现在向下滚动到Misc部分,找到选项“当没有证书或只有一个证书时,不要提示客户端证书选择”Diable。

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

如果看不到mvc.localhost,那么您的客户端证书不在相应的商店中。

答案 2 :(得分:0)

添加到Bogdan的正确答案(因为我有同样的问题),我将OID_PKIX_KP_CLIENT_AUTH OID1.3.6.1.5.5.7.3.2)添加到-eku参数中,这给了我两个目的是你可能想要的而不是暴力&#34;所有应用程序政策&#34;

e.g。 (删除明显的换行符)

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