当不应该使用会员凭证时,出现了会员凭证验证失败的问题。

18

由于我们的网站不能直接接受信用卡付款,因此我们会将用户带到另一个站点上的“托管页面”中,其中包含验证凭据和其他杂项变量。

更详细地说,用户通常会按照以下方式访问:

  1. 转到我们的网站,并使用他们以前创建的用户名和密码登录。这使用asp.net成员身份提供程序。

  2. 一旦登录,我们会向他们显示账户,他们可以通过一个按钮进行付款。一旦他们点击了这个按钮...

  3. 他们将被提示输入“预付款”页面以验证金额和其他各种信息。从这里继续点击...

  4. 因此,支付页面将在我们网站的iframe中显示。我们使用以下代码将它们重定向到外部托管网页:

    <div align="center"> <iframe width="100%" height="600px" src="@Html.Raw(@ViewBag.GateWayWebsite)"> </div>

  5. 一旦输入了付款页面并且顾客点击提交,该网站会向我们开始的网站提交后置请求,并传回有关收费的信息。我获取此信息并将其保存到我们的数据库中并显示收据。

除了步骤#5之外,一切都运行良好。尽管大多数时间都有效,但有约10%的情况会返回此消息:

Event code: 4006 
Event message: Membership credential verification failed. 
Event time: 12/16/2013 4:32:22 AM 
Event time (UTC): 12/16/2013 12:32:22 PM 
Event ID: 42c509f2a25d46f0af17e72a52dfbbe5 
Event sequence: 38 
Event occurrence: 1 
Event detail code: 0 

Application information: 
    Application domain: /LM/W3SVC/3/ROOT/SuburbanCustPortal-1-130316693110399868 
    Trust level: Full 
    Application Virtual Path: /SuburbanCustPortal 
    Application Path: C:\inetpub\wp\SuburbanCustPortal\ 
    Machine name: WIN-OB929P97YAR 

Process information: 
    Process ID: 3620 
    Process name: w3wp.exe 
    Account name: NT AUTHORITY\NETWORK SERVICE 

Request information: 
    Request URL: https://myurl:443/SuburbanCustPortal/Account/Logon2 
    Request path: /SuburbanCustPortal/Account/Logon2 
    User host address: xx.xx.xx.xx 
    User:  
    Is authenticated: False 
    Authentication Type:  
    Thread account name: NT AUTHORITY\NETWORK SERVICE 

Name to authenticate: testuser 

我无法在我运行的少数几个测试用例中发生这种情况,这使得它变得更加令人沮丧。

以下是我的web.config文件:

<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=152368
  -->
<configuration>

  <appSettings>
    <add key="webpages:Version" value="1.0.0.0"/>
    <add key="ClientValidationEnabled" value="true"/>
    <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
    <add key="suburbanServiceUrl" value=""/>
  </appSettings>

  <system.web>

    <sessionState
      mode="InProc"
      stateConnectionString="tcpip=127.0.0.1:42424"
      stateNetworkTimeout="60"
      sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
      cookieless="false"
      timeout="60"
    />

    <customErrors mode="Off"/>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
        <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
      </assemblies>
    </compilation>
    <authentication mode="Forms">
      <!-- timeout: Gets and sets the amount of time, in minutes, allowed between requests
                    before the session-state provider terminates the session. -->
      <forms loginUrl="~/Account/LogOn" timeout="60"/>
    </authentication>

    <membership>
      <providers>
        <clear/>
        <add name="AspNetSqlMembershipProvider"
             type="System.Web.Security.SqlMembershipProvider"
             connectionStringName="ApplicationServices"
             enablePasswordRetrieval="false"
             enablePasswordReset="true"
             requiresQuestionAndAnswer="false"
             requiresUniqueEmail="true"
             maxInvalidPasswordAttempts="30"
             minRequiredPasswordLength="6"
             minRequiredNonalphanumericCharacters="0"
             passwordAttemptWindow="10"
             applicationName="webportal"/>
      </providers>

    </membership>

    <profile>
      <providers>
        <clear/>
        <add name="AspNetSqlProfileProvider" type="System.Web.Profile.SqlProfileProvider" connectionStringName="ApplicationServices" applicationName="/"/>
      </providers>
    </profile>

    <roleManager enabled="true">
      <providers>
        <clear/>
        <add name="AspNetSqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="ApplicationServices" applicationName="/"/>
        <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/"/>
      </providers>
    </roleManager>

    <pages enableSessionState="true">
      <namespaces>
        <add namespace="System.Web.Helpers"/>
        <add namespace="System.Web.Mvc"/>
        <add namespace="System.Web.Mvc.Ajax"/>
        <add namespace="System.Web.Mvc.Html"/>
        <add namespace="System.Web.Routing"/>
        <add namespace="System.Web.WebPages"/>
      </namespaces>
    </pages>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="Session"/>
      <add name="Session" type="System.Web.SessionState.SessionStateModule"/>
    </modules>
    <httpProtocol>
    </httpProtocol>
    <staticContent>
      <clientCache cacheControlCustom="public"
      cacheControlMaxAge="00:00:01" cacheControlMode="UseMaxAge" />
    </staticContent>   
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0"/>
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_ISuburbanService" maxReceivedMessageSize="128072" />
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:2181/ISuburbanService.svc"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISuburbanService"
        contract="SuburbanService.ISuburbanService" name="BasicHttpBinding_ISuburbanService" />
    </client>
    <!--<bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_ISuburbanService" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
          maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
          messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
          useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="Transport">
            <transport clientCredentialType="Basic" proxyCredentialType="None"
              realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="https://localhost/SuburbanHUB/ISuburbanService.svc"
        binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ISuburbanService"
        contract="SuburbanService.ISuburbanService" name="BasicHttpBinding_ISuburbanService" />
    </client>-->
    <!--<behaviors>
      <serviceBehaviors>
        <behavior name="SomeServiceServiceBehavior">
          <serviceDebug includeExceptionDetailInFaults="true"/>
          <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>-->
  </system.serviceModel>
</configuration>

捕获POST请求的方法:

  [NoCache]
    [HttpPost]
    public ActionResult Receipt(string id)
    {
      var sb = new StringBuilder();
      try
      {
        sb.AppendLine("ActionResult Reciept(string account)");

        var count = 0;
        var postVals = new Dictionary<string, string>();
        foreach (var key in Request.Form.AllKeys)
        {
          sb.AppendLine("count: " + count);
          sb.AppendLine(string.Format("key:   {0}    Value:   {1}", key, Request.Form[key]));
          postVals.Add(key, Request.Form[key]);
          sb.AppendLine("finished count: " + count);
          count++;
        }
        sb.AppendLine("finished processing ALLKeys");
        var paymentReq = createPaymentRequest(postVals);
        sb.AppendLine("finished processing 'var paymentReq = createPaymentRequest(postVals)' ");
        var receipt = _client.RecordPaymentWithRequest(paymentReq);

        var retval = PartialView(receipt.Duplicate ? "Duplicate Receipt" : "Receipt", receipt);
        sb.AppendLine(string.Format("retval: {0}", retval));
        return retval;

      }
      catch (Exception ex)
      {
        sb.AppendLine(string.Format("Receipt error: {0}", ex.Message));
        Logging.LogException("Receipt error!", ex, _asName);
        throw;
      }
      finally
      {
        Logging.LogInfo(sb.ToString(), _asName);
      }
    }

如上所示,可以看到我没有在其中加入[Authorize],因此不应要求成员提供程序检查访问权限。类级别也不需要。

有人有什么建议吗?

更新

2013-12-16 04:22:14 xxx.xxx.xxx.xxx GET /SuburbanCustPortal/Scripts/Views/logon.js - 443 - xxx.xxx.xxx.xxx Mozilla/5.0+(Linux;+Android+4.2.2;+en-us;+SAMSUNG+SGH-M919+Build/JDQ39)+AppleWebKit/535.19+(KHTML,+like+Gecko)+Version/1.0+Chrome/18.0.1025.308+Mobile+Safari/535.19 304 0 0 109
2013-12-16 04:22:14 xxx.xxx.xxx.xxx GET /SuburbanCustPortal/Content/images/mod/modavoca.png - 443 - xxx.xxx.xxx.xxx Mozilla/5.0+(Linux;+Android+4.2.2;+en-us;+SAMSUNG+SGH-M919+Build/JDQ39)+AppleWebKit/535.19+(KHTML,+like+Gecko)+Version/1.0+Chrome/18.0.1025.308+Mobile+Safari/535.19 304 0 0 93
2013-12-16 04:22:15 xxx.xxx.xxx.xxx GET /Content/favicon.ico - 443 - xxx.xxx.xxx.xxx Mozilla/5.0+(Linux;+Android+4.2.2;+en-us;+SAMSUNG+SGH-M919+Build/JDQ39)+AppleWebKit/535.19+(KHTML,+like+Gecko)+Version/1.0+Chrome/18.0.1025.308+Mobile+Safari/535.19 404 0 2 250
2013-12-16 04:22:15 xxx.xxx.xxx.xxx GET /apple-touch-icon-precomposed.png - 443 - xxx.xxx.xxx.xxx Mozilla/5.0+(Linux;+Android+4.2.2;+en-us;+SAMSUNG+SGH-M919+Build/JDQ39)+AppleWebKit/535.19+(KHTML,+like+Gecko)+Version/1.0+Chrome/18.0.1025.308+Mobile+Safari/535.19 404 0 2 250
2013-12-16 04:22:15 xxx.xxx.xxx.xxx GET /apple-touch-icon.png - 443 - xxx.xxx.xxx.xxx Mozilla/5.0+(Linux;+Android+4.2.2;+en-us;+SAMSUNG+SGH-M919+Build/JDQ39)+AppleWebKit/535.19+(KHTML,+like+Gecko)+Version/1.0+Chrome/18.0.1025.308+Mobile+Safari/535.19 404 0 2 78
#Software: Microsoft Internet Information Services 7.0
#Version: 1.0
#Date: 2013-12-16 04:39:52
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) sc-status sc-substatus sc-win32-status time-taken
2013-12-16 04:39:52 xxx.xxx.xxx.xxx GET / - 443 - xxx.xxx.xxx.xxx - 200 0 0 171
2013-12-16 04:50:12 xxx.xxx.xxx.xxx POST /SuburbanHUB/ISuburbanService.svc - 443 suburbansoftware xxx.xxx.xxx.xxx - 200 0 0 875
2013-12-16 04:50:12 xxx.xxx.xxx.xxx POST /SuburbanHUB/ISuburbanService.svc - 443 suburbansoftware xxx.xxx.xxx.xxx - 200 0 0 187
2013-12-16 04:50:12 xxx.xxx.xxx.xxx GET /SuburbanCustPortal/Account/Verify id=dde4bbfb-0d2e-4706-a604-36eea3fdcae3&verifyid=c0b4fdb5-9bb3-4d2b-b724-df42e6ea2a59 443 - xxx.xxx.xxx.xxx Mozilla/5.0+(iPhone;+CPU+iPhone+OS+7_0_3+like+Mac+OS+X)+AppleWebKit/537.51.1+(KHTML,+like+Gecko)+Version/7.0+Mobile/11B511+Safari/9537.53 200 0 0 1328
2013-12-16 04:50:12 xxx.xxx.xxx.xxx GET /SuburbanCustPortal/Content/reset.css - 443 - xxx.xxx.xxx.xxxMozilla/5.0+(iPhone;+CPU+iPhone+OS+7_0_3+like+Mac+OS+X)+AppleWebKit/537.51.1+(KHTML,+like+Gecko)+Version/7.0+Mobile/11B511+Safari/9537.53 200 0 0 453

从04:22:15到04:39:52的日志中有间隔.

这正常吗?

编辑

我已经为那些问过的人澄清了上述步骤。


你有这些请求的IIS日志吗?事件日志显示特定页面正在被请求,这意味着某个东西正在重定向到该页面。日志将向您显示之前请求的页面。 - Matthew Steeples
@MatthewSteeples 我会检查并发布结果。 - ErocM
1
你可能需要放弃一些Logging.LogInfo行,尝试缩小错误发生的范围。虽然这很繁琐,但在这种情况下是有效的。 - Kao
2
有件事困扰着我。收据操作似乎没有以任何方式授权POST请求。它似乎很容易被滥用。我是不是漏了什么,或者你的设计缺乏适当的安全性? - Wiktor Zychla
1
这些澄清细节应该根据情况添加到问题或答案中;评论可以随时被删除。 - George Stocker
显示剩余23条评论
6个回答

5
那可真是在那个时间发生非常可疑啊。我同意你的观点,但在步骤#4中,似乎你将用户重定向离开了当前网站,然后用户进行数据输入。如果由于分心等原因用户偶尔需要花费10-20分钟来输入信息,那么这种情况就比简单的竞态条件更有可能发生。如果你仍然拥有所有错误数据,你可以回溯查看是否能够找到这种情况发生的时间模式(或每xx小时一次——见下文)。
1. 检查IIS设置,看看应用程序池何时会回收。它是否每天晚上4点左右会回收?它是否按轮换计划回收?默认情况下,IIS以某个奇怪的小时数回收(我认为是每28小时)。
2. 切换到State Server(或SQL)状态,不要使用InProc Session状态。长期来看,InProc只会给你带来痛苦。请注意,当你进行此更改时,你必须确保放入会话中的所有对象都是可序列化的,否则会出现错误。InProc在会话中不需要对象被序列化。
编辑:好的,请按以下方式检查您的应用程序池回收:
1. 在IIS管理器中,选择适当的应用程序池并选择“高级设置”(右键单击或使用右侧菜单)。 2. 滚动到底部,到回收部分。 3. “定期时间间隔”将每xx分钟重置应用程序池。默认值为1740分钟,即每29小时一次。 4. “特定时间”设置允许您设置计划时间来进行回收。
总的来说,您确实需要定期回收应用程序池(可能是每天一次)。
回答您的第二个问题:如果这确实是原因,那不是超时的问题,而是应用程序池是否在用户被重定向离开并重新重定向回来的这段时间内回收的问题。将会话状态更改为除InProc以外的其他状态应该可以解决此问题。
话虽如此,会话过期也可能是导致此错误的原因,因此将会话超时设置为较大的值也可以解决此问题。如果您对发生这种情况的记录进行更全面的查看,可能会提供更多的线索。
编辑#2:
请尝试在日志中隔离出错误的发生时间。如果你能够这样做,看看是否有浏览器的模式被使用。我还会寻找其他模式,看看是否有任何跳出来的东西。
你可以尝试使用一堆不同的浏览器(包括移动端)来测试,以查看是否可以复现。此外,请尝试不同版本的IE和IE上的不同安全设置。

能告诉我在哪里可以找到IIS池何时进行回收的信息吗?这经常需要运行吗? - ErocM
此外,应该按建议将超时时间延长到10-20分钟,在他们停留在重定向页面的情况下。 - ErocM
你是否在Global ASAX postAuthenticaton事件中使用逻辑?你确定你的逻辑没有假设后续步骤使用相同的线程吗?当静态变量在身份验证后被使用时,我曾经看到过非常类似的问题。 - phil soady
似乎每次都会失败,对吧?不只是十次中的一次吗? - Phil Sandler
@PhilSandler 很棒的帖子。我明天会检查这些设置。感谢大家的意见。 - ErocM
显示剩余7条评论

1

我已经有一段时间没有解决这样的问题了(但我在7年前为pss做过)。

@PhilSandler 建议的所有内容都很好。然而,我有一种直觉,根本原因不是AppPool回收或会话超时,如果你绝望了,可以尝试 Out-Of-Process Session State

我认为如果大约有10个用户中的1个遇到问题,并且出现间歇性(因为你无法重现它),那么可能是浏览器特定设置的问题。我猜遇到问题的用户在其浏览器中禁用了cookie(FF和Chrome用户比普通IE用户更聪明)。

这个假设与@SilverlightFox理论略有相似:

阻止第三方cookie的框架


<authentication mode="Forms">

我觉得表单身份验证无法在客户端存储会话数据。由于禁用了cookie,可能会导致会员凭据验证失败的事件日志条目。
请在Firefox或Chrome中禁用cookie,并尝试验证我的假设。希望这能让您复现它。

明天我会试一试。谢谢你的想法。 - ErocM
如果这不是根本原因,请告诉我,即使悬赏已经被颁发,我也很有兴趣跟进这个问题。 - Jeremy Thompson
我测试过了,用户在没有我的捕获所有错误方法的情况下无法通过登录界面。我尝试了几种不同的cookie设置变体。我发现我不想再使用它们了 :) 现在我打算完全放弃它们,因为它们在我的网站上引起了问题。 - ErocM

1
请参考ScottGu的以下文章http://weblogs.asp.net/scottgu/archive/2006/04/22/Always-set-the-_2200_applicationName_2200_-property-when-configuring-ASP.NET-2.0-Membership-and-other-Providers.aspx。此外,请仔细检查“aspnet_Applications”数据库表中的“ApplicationName”值以及web.config文件中所有提供程序(成员资格提供程序、角色提供程序、配置文件提供程序等)的“applicationName”属性值,确保它们具有相同的值。希望这能解决你的问题!

1

在网上搜索后,我找到了一些解决方案,这些方案可以解决4006错误并最终解决问题。

  • 将applicationName字段设置为"/"。- 这是关于你的问题最常见的讨论。但是,你的applicationName字段看起来很好,所以肯定不是这个问题。

  • ActiveDirectoryMembershipProvider存在问题 - 链接。这似乎不太可能是你的问题,因为你正在使用SqlMembershipProvider

  • Internet用户缺少对.mdf和.ldf文件的权限 - 链接。我看到的有关此问题的参考是人们使用SQLExpress,因为它在ASP.NET用户帐户下运行并需要读/写访问权限。再次强调,这似乎不太可能是你的问题,因为你只是偶尔遇到这个问题。但与4006错误相关,因此验证用户权限值得一看。

  • 重复的用户密码 - 我没有保存链接,但如果数据库中没有用户名和密码的唯一约束,则会返回4006响应。到目前为止,这似乎对我来说最有可能,因为它可以解释间歇性行为,并且人们在使用互联网时经常双击。我会仔细检查你的约束条件。

我很乐意看看我能找到什么其他的东西,但首先排除最后两个似乎是一个好主意。另外,你日志中间的空白最奇怪的事情似乎是EventLog数据显示错误几乎发生在IIS空白期的中间。然后在恢复之后,又有了另一个日志空白期。看起来可能是两个完全不同的用户。也许不是,只是想提一下。告诉我吧。祝你好运。


1
我看到楼主没有对这篇帖子进行负面评价。在赏金中获得2个或更多的赞就意味着即使你没有帮助楼主,你也会得到奖励。为什么有人会对一个明显试图排除和询问手头问题可能原因的回复进行负面评价,而且不给出任何理由呢?嗯,我想知道原因。 - drankin2112
1
我没有在这个帖子上投反对票。这是有用的信息,感谢帮助! - ErocM

1
我在你的web.config中发现了以下几点:
  1. 你有多个applicationName。通常,如果applicationName不同,IsUserInRole和GetRolesForUser应该会失败。

  2. 此外,你需要为每个提供程序设置defaultProvider,特别是如果你有多个提供程序,比如roleManager - <roleManager ... defaultProvider="DefaultRoleProvider">

当前Web.config

<membership>
   <providers>
      <clear/>
          <add ... applicationName="webportal"/>
   </providers>
</membership>

<profile>
   <providers>
      <clear/>
      <add ... applicationName="/"/>
    </providers>
</profile>

<roleManager enabled="true">
   <providers>
      <clear/>
      <add ... applicationName="/"/>
      <add ...  applicationName="/"/>
   </providers>
</roleManager>

看看 Scott Hanselman 的 ASP.NET Universal Providers 博客的 web.config 文件。

<sessionState
   mode="InProc"
   stateConnectionString="tcpip=127.0.0.1:42424"
   stateNetworkTimeout="60"
   sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI"
   cookieless="false"
   timeout="60"
/>

对于 InProc,您不需要使用 stateConnectionStringstateNetworkTimeoutsqlConnectionString。更多信息 在这里


0

我注意到在IFrame中src没有正确设置。

<div align="center"> <iframe width="100%" height="600px" src="@Html.Raw(@ViewBag.GateWayWebsite)"></iframe></div>

GateWayWebsite 应该进行适当的 HTML 编码,以防它包含相关但未被正确传递的特殊字符:

<div align="center"> <iframe width="100%" height="600px" src="@ViewBag.GateWayWebsite"></iframe></div>

另一个想法是可能是帧集阻止第三方cookie的设置,可能是浏览器设置或者P3P隐私策略头部。试着在IFrame之外打开链接(即地址栏会改变为外部站点),以测试是否解决了你的问题。

编辑:我不应该编辑我的答案,因为你已经撤销了你的赞同。我的回答在技术上是正确的,尽管它可能不是解决你当前问题的答案,但我认为URL的输出是错误的,因为你没有对它进行HTML编码。我发现最好把所有东西都修好,这样就可以减少未来出现棘手的难以调试的bug的机率。


ViewBag没有使用html.raw来嵌入iframe时无法正常工作。它会将URL放在指定位置,否则它将显示为文本。 - ErocM
@ErocM:我不确定你的意思... @ViewBag.GateWayWebsite 将由 Razor 引擎呈现。你有一个 GateWayWebsite URL 的例子吗? - SilverlightFox

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