浏览器不会在支付网关对我们网站的发布请求上设置ASP.NET_SessionId cookie



我们在web应用程序的支付过程中遇到了一个奇怪的问题,导致会话数据丢失。

在这个过程中,在我们的结账页面后,用户被重定向到支付提供商的页面,并在完成后立即重定向回我们的网站(到我们指定的url)。最后一次重定向是通过浏览器评估支付提供商的html代码来完成的,该代码基本上包括一个发布到我们网站的表单和几行在页面加载时发布该表单的javascript代码。此时,浏览器会发出post请求,但不会设置"ASP.NET_SessionId"cookie,该cookie存在于之前向完全相同的域(我们的应用程序的域)发出的请求中。更奇怪的是,它设置了另一个我们使用的cookie,名为"AcceptCookie"。它只是简单地选择删除"ASP.NET_SessionId"cookie。

为了说明这种情况,我拍了一些截图。(在这些屏幕截图中,橙色和绿色矩形包含完全相同的值。)

  1. 这是用户按下"签出"按钮时(向我们的应用程序)发出的请求。在此请求之后,用户被重定向到支付提供商的页面

退房请求

  1. 这是用户完成后支付提供商提供的最后一页。正如你所看到的,这只是一个简单的表单,它会在页面加载时自动发布到我们的域中

支付提供商的最终响应

  1. 但此发布请求不包括"ASP.NET_SessionId"cookie,这会导致获取新的会话id并丢失以前的会话数据。同样,只缺少"ASP.NET_SessionId",而没有另一个名为"AcceptCookie"的

发布请求,将用户带回我们的网站(在上一步中使用javascript制作)

最后,我们发现在旧版本的浏览器上不会出现这个问题。在Firefox 52上,它就像一个魅力,但在Firefox 71上,会出现上述问题。

有什么想法吗?

注意:这是一个带有targetFramework="4.5.2"的ASP.NET MVC应用程序

祝你今天愉快。

我们想明白了。

不知怎的,"ASP.NET_SessionId"cookie的"SameSite"属性默认为"Lax",这导致会话cookie没有添加到由支付网关的javascript代码发出的请求中。

我们在web.config文件中添加了以下规则,以便覆盖此值并将其设置为"无"。

<configuration>
<system.webServer>
<rewrite>
<outboundRules>
<rule name="Add SameSite" preCondition="No SameSite">
<match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
<action type="Rewrite" value="{R:0}; SameSite=None" />
<conditions>
</conditions>
</rule>
<preConditions>
<preCondition name="No SameSite">
<add input="{RESPONSE_Set_Cookie}" pattern="." />
<add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=None" negate="true" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
</system.webServer>
</configuration>

更新1:仅添加上述配置就解决了现代浏览器的问题,但我们意识到,旧版本的Micosoft Edge和Internet Explorer仍然存在问题。

因此,我们需要将cookieSameSite="None"属性添加到web.config文件中的sessionState节点。

<sessionState cookieSameSite="None" />

不过,请小心此配置更改,因为较旧的.net框架版本不支持它,并导致您的网站显示错误页面

顺便说一句,我们在IOS 12中的浏览器仍然存在问题。但我认为这与这个已确认的错误有关

更新2:关于IOS问题的可能修复,请参阅zemien的回答

更新3:通过将我们的发现与泽米恩答案中的建议相结合,我们提出了以下重写规则。我们一直在生产中使用这种配置但请注意:对于兼容的浏览器,它会将所有cookie标记为"SameSite:None"属性,而对于不兼容的浏览器则会排除SameSite属性(如果存在)这看起来可能很复杂,但我试图通过评论线进行解释。

这是我们在生产中使用的最终配置:

<configuration> 
<system.webServer>
<rewrite>
<outboundRules>
<preConditions>
<!-- Browsers incompatible with SameSite=None -->
<preCondition name="IncompatibleWithSameSiteNone" logicalGrouping="MatchAny">
<add input="{HTTP_USER_AGENT}" pattern="(CPU iPhone OS 12)|(iPad; CPU OS 12)" />
<add input="{HTTP_USER_AGENT}" pattern="(Chrome/5)|(Chrome/6)" />
<add input="{HTTP_USER_AGENT}" pattern="( OS X 10_14).*(Version/).*((Safari)|(KHTML, like Gecko)$)" />
</preCondition>
<!-- Rest of the browsers are assumed to be compatible with SameSite=None -->
<preCondition name="CompatibleWithSameSiteNone" logicalGrouping="MatchAll">
<add input="{HTTP_USER_AGENT}" pattern="(CPU iPhone OS 12)|(iPad; CPU OS 12)" negate="true" />
<add input="{HTTP_USER_AGENT}" pattern="(Chrome/5)|(Chrome/6)" negate="true" />
<add input="{HTTP_USER_AGENT}" pattern="( OS X 10_14).*(Version/).*((Safari)|(KHTML, like Gecko)$)" negate="true" />
</preCondition>
</preConditions>
<!-- Rule 1: Remove SameSite part from cookie for incompatible browsers if exists -->
<rule name="Remove_SameSiteCookie_IfExists_ForLegacyBrowsers" preCondition="IncompatibleWithSameSiteNone">
<match serverVariable="RESPONSE_Set-Cookie" pattern="(.*)(SameSite=.*)" />
<action type="Rewrite" value="{R:1}" />
</rule>
<!-- Rule 2: Override SameSite's value to None if exists, for compatible browsers -->
<rule name="Override_SameSiteCookie_IfExists_ForModernBrowsers" preCondition="CompatibleWithSameSiteNone">
<match serverVariable="RESPONSE_Set-Cookie" pattern="(.*)(SameSite=.*)" />
<action type="Rewrite" value="{R:1}; SameSite=None" />
</rule>
<!-- Rule 3: Add SameSite attribute with the value None if it does not exists, for compatible browsers -->
<rule name="Add_SameSiteCookie_IfNotExists_ForModernBrowsers" preCondition="CompatibleWithSameSiteNone">
<match serverVariable="RESPONSE_Set-Cookie" pattern=".*"/>
<!-- Condition explanation: Cookie data contains some string value but does not contain SameSite attribute -->
<conditions logicalGrouping="MatchAll">
<add input="{R:0}" pattern="^(?!s*$).+"/>
<add input="{R:0}" pattern="SameSite=.*" negate="true"/>
</conditions>
<action type="Rewrite" value="{R:0}; SameSite=None" />
</rule>
</outboundRules>
</rewrite>    
</system.webServer>  
</configuration>

我修改了几个SO的答案,提出了这个URL重写,将SameSite=None添加到会话cookie中,并从大多数不兼容浏览器的所有cookie中删除SameSite=None。这次重写的目的是保留Chrome 80之前的"遗留"行为。

在我的Coder Frontline博客中完整地写下:

<rewrite>
<outboundRules>
<preConditions>
<!-- Checks User Agent to identify browsers incompatible with SameSite=None -->
<preCondition name="IncompatibleWithSameSiteNone" logicalGrouping="MatchAny">
<add input="{HTTP_USER_AGENT}" pattern="(CPU iPhone OS 12)|(iPad; CPU OS 12)" />
<add input="{HTTP_USER_AGENT}" pattern="(Chrome/5)|(Chrome/6)" />
<add input="{HTTP_USER_AGENT}" pattern="( OS X 10_14).*(Version/).*((Safari)|(KHTML, like Gecko)$)" />
</preCondition>
</preConditions>
<!-- Adds or changes SameSite to None for the session cookie -->
<!-- Note that secure header is also required by Chrome and should not be added here -->
<rule name="SessionCookieAddNoneHeader">
<match serverVariable="RESPONSE_Set-Cookie" pattern="((.*)(ASP.NET_SessionId)(=.*))(SameSite=.*)?" />
<action type="Rewrite" value="{R:1}; SameSite=None" />
</rule>
<!-- Removes SameSite=None header from all cookies, for most incompatible browsers -->
<rule name="CookieRemoveSameSiteNone" preCondition="IncompatibleWithSameSiteNone">
<match serverVariable="RESPONSE_Set-Cookie" pattern="(.*)(SameSite=None)" />
<action type="Rewrite" value="{R:1}" />
</rule>
</outboundRules>
</rewrite>

这应该适用于大多数ASP.Net和ASP.Net Core应用程序,尽管较新的Frameworks有适当的代码和配置选项来控制这种行为。在使用我上面的重写之前,我建议你研究一下所有可用的选项。

如果你不想部署安全的SameSite=Nonecookie,那么另一个选择就是让浏览器在页面加载时用JavaScript设置cookie。这种方法适用于所有浏览器,而不需要针对各种浏览器的任何特殊情况。在ASP。NET MVC应用程序,只需在layout.cshtml页面中添加以下内容即可实现:

<script type="text/javascript">
// If being rendered in an iFrame, set a client-side cookie for the ASP.NET Session ID
if (window != window.top) {
document.cookie = "ASP.NET_SessionID=@HttpContext.Current.Session.SessionID";
}
</script>

这是通过HTML有效载荷将cookie值有效地传递给客户端,然后客户端覆盖浏览器可能接受或不接受的ASP.NET_SessionIDcookie。一旦设置了cookie,则发出的任何请求都会将cookie传回服务器。请注意,这种方法不允许您为会话cookie指定HttpOnly

我不建议将这种方法用于面向公众的网站,但对于intranet应用程序,这是一种非常快速的解决方法。

最新更新