Chrome 84 支付跳转后 ASP.NET Web Forms Session 超时
结论
跨站支付回跳后 Session 丢失,通常是 Chrome 没有发送 Session Cookie,并不一定是服务器端的 Session 真正超时。
支付网关通过跨站 POST 返回网站时,Chrome 84 会按照新版 SameSite 规则处理 Cookie。如果 ASP.NET Web Forms 的 ASP.NET_SessionId 或 Forms Authentication Cookie 没有正确设置为 SameSite=None; Secure,回跳请求可能不会携带这些 Cookie。服务器收到请求后会创建新的 Session,应用就会误判原 Session 已超时,并把用户视为未登录。
建议这样处理:
- 将应用升级到至少 .NET Framework 4.7.2。最好使用仍受支持、已安装最新系统更新的 .NET Framework 4.8。
- 全站启用 HTTPS。
- 将 Session Cookie 和身份验证 Cookie 配置为
SameSite=None; Secure。 - 不要只通过 Session 确认支付结果。应使用订单号和支付网关的服务端通知恢复交易上下文。
为什么超过一段时间后才出现
Chrome 会把未声明 SameSite 的 Cookie 默认视为 SameSite=Lax。跨站顶层导航通常可以携带这类 Cookie,但跨站 POST 回跳会受到限制。
部分 Chrome 版本曾为刚创建不久的 Cookie 提供临时兼容机制,允许它在短时间内随跨站 POST 发送。这就可能造成一种现象:用户很快完成支付时一切正常,在支付页面停留较久后,Session 却丢失了。
这个兼容窗口不能作为可靠的业务机制,也不应根据它设置 90 秒或其他超时时间。实际表现还会受到这些因素影响:
- 支付网关使用
POST还是GET回跳; - Session Cookie 的创建时间;
- Chrome 版本和策略;
- IIS、负载均衡器或应用程序池的 Session 配置;
- 支付网关自身的交易超时。
可以在 Chrome 开发者工具的 Network 面板中检查回跳请求。如果请求中没有原来的 ASP.NET_SessionId,基本可以确定问题出在 Cookie 或 SameSite,而不是服务器主动删除了 Session。
推荐配置
以下配置要求运行时已经支持 SameSite。对于 ASP.NET,通常需要使用已安装相应更新的 .NET Framework 4.7.2 或更高版本。
<configuration>
<system.web>
<httpCookies
sameSite="None"
requireSSL="true" />
<sessionState
cookieSameSite="None" />
<authentication mode="Forms">
<forms
loginUrl="~/Login.aspx"
timeout="30"
requireSSL="true"
cookieSameSite="None" />
</authentication>
</system.web>
</configuration>
这些配置分别处理不同的 Cookie:
sessionState cookieSameSite="None":处理ASP.NET_SessionId。forms cookieSameSite="None":处理 Forms Authentication Cookie。httpCookies sameSite="None":为应用创建的其他 ASP.NET Cookie 设置默认值。requireSSL="true":为 Cookie 添加Secure属性。
现代 Chrome 要求 SameSite=None 必须与 Secure 同时使用,所以站点必须通过 HTTPS 访问。如果仍允许 HTTP 请求,这些请求不会携带 Secure Cookie。
配置完成后,响应中的 Cookie 应类似:
Set-Cookie: ASP.NET_SessionId=...; path=/; secure; HttpOnly; SameSite=None
身份验证 Cookie 也应包含相同的关键属性:
Set-Cookie: .ASPXAUTH=...; path=/; secure; HttpOnly; SameSite=None
.NET Framework 4.5 应如何处理
.NET Framework 4.5 发布时还没有现在的 SameSite 语义,仅在旧版配置中增加一行设置,无法保证它能正确工作。直接在旧运行时中加入:
<httpCookies sameSite="None" />
可能导致配置项无法识别、输出格式错误,或者不同补丁环境下的行为不一致。
更稳妥的做法是升级应用的目标框架,同时确认服务器已安装对应的 .NET Framework 和系统更新。Web Forms 应用从 4.5 升级到 4.7.2 或 4.8 通常不需要重写业务代码,但仍需完整测试以下内容:
- 登录和退出;
- Session 创建与续期;
- 支付网关的成功、失败和取消回跳;
- Forms Authentication;
- HTTPS 重定向;
- 第三方控件及 NuGet 依赖;
- IIS 应用程序池配置。
如果短期内确实无法升级,可以暂时在响应层手工改写 Set-Cookie,或者通过反向代理添加属性。不过,这类方案比较脆弱,可能重复添加属性、遗漏 Cookie,或者破坏包含特殊字符的 Cookie 头,因此只适合作为临时措施。除非已经验证过所有 Cookie 和客户端范围,否则不要使用简单的字符串拼接统一修改全部响应头。
验证方法
登录后先记录当前 Cookie,再执行一次超过问题时间的支付流程。检查回跳请求的 Request Headers,重点确认是否包含:
Cookie: ASP.NET_SessionId=...
还需要逐项核对:
- 回跳前后的
ASP.NET_SessionId是否相同; .ASPXAUTH是否仍被发送;- Cookie 是否显示
SameSite=None; - Cookie 是否显示
Secure; - 回跳地址是否确实使用 HTTPS;
- 支付网关是否通过跨站
POST回跳。
如果原 Session Cookie 已随请求发送,但服务器仍然创建了新 Session,就要继续排查服务端问题,例如:
- IIS 应用程序池在支付期间发生回收;
- 使用 InProc Session,而请求被负载均衡到另一台服务器;
web.config或站点文件发生变化,导致应用重启;- Session 的
timeout确实过短; - 多节点服务器没有共享 Session State;
- 机器密钥或 Forms Authentication 配置不一致。
支付流程不要完全依赖 Session
即使 Cookie 配置正确,也不应把 Session 作为支付结果与订单之间的唯一关联方式。用户可能关闭浏览器或禁用 Cookie,支付网关也可能重复发送通知或延迟回调。
发起支付时,应生成不可猜测的交易标识,并将它与订单一起写入数据库:
string transactionId = Guid.NewGuid().ToString("N");
// 保存 transactionId、orderId、userId、金额和初始状态到数据库
// 将 transactionId 作为支付网关允许的商户订单号或 state 参数发送
支付返回后,通过交易标识查询订单,并验证网关签名:
protected void PaymentReturn(object sender, EventArgs e)
{
string transactionId = Request.Form["transaction_id"];
if (string.IsNullOrWhiteSpace(transactionId))
{
Response.StatusCode = 400;
return;
}
// 1. 根据 transactionId 从数据库加载订单
// 2. 验证支付网关签名
// 3. 核对商户号、订单号、金额、币种和支付状态
// 4. 以幂等方式更新订单
// 5. Session 仅用于改善用户体验,不作为支付真实性依据
}
字段名称和签名算法必须以具体支付网关的接口文档为准。支付成功状态最好通过服务端通知或主动查询接口确认,浏览器回跳只用于向用户展示结果。
直接的修复方法是升级到受支持的 .NET Framework,并为相关 Cookie 设置 SameSite=None; Secure。从长期来看,支付确认流程还应能够脱离 Session 独立完成。
备注:内容仅供参考。
