WordPress 在 Chrome 88 中丢失 session
结论
不要通过 URL 传递 WooCommerce session ID,也不要在回跳后手动重建登录 Cookie。更安全、可靠的做法是:
- 确认第三方支付页面是否通过跨站
POST返回。 - 分开处理支付回调和浏览器回跳。
- 服务端验证支付结果后,通过
303跳转到站内的GET页面。 - 只有业务确实需要在跨站请求中携带 Cookie 时,才考虑使用
SameSite=None; Secure,同时部署严格的 CSRF 防护和回调签名校验。
Chrome 88 前后对 SameSite Cookie 策略的执行比部分浏览器更严格。未明确指定 SameSite 的 Cookie 通常按 Lax 处理,而跨站 POST 请求一般不会携带这类 Cookie。第三方支付站点返回 WordPress 时,WooCommerce session Cookie 和登录 Cookie 因此可能暂时不可见。
先确认丢失的是 Session 还是其中一个字段
下面的检查只能说明 chosen_payment_method 不存在,不能据此判断整个登录状态或 WooCommerce session 已经丢失:
isset(WC()->session->chosen_payment_method)
应改用 WooCommerce session API,同时检查当前用户和 session Cookie:
$chosen_payment_method = WC()->session
? WC()->session->get('chosen_payment_method')
: null;
error_log('Logged in: ' . (is_user_logged_in() ? 'yes' : 'no'));
error_log('Payment method: ' . var_export($chosen_payment_method, true));
error_log('Cookies: ' . wp_json_encode(array_keys($_COOKIE)));
不要在日志中记录 Cookie 的完整值,其中可能包含能够识别或接管会话的数据。
还要在 Chrome DevTools 的 Network 面板中检查返回请求:
- 返回方式是
GET还是POST - Request Headers 中是否包含
Cookie - Application → Cookies 中 Cookie 的
SameSite、Secure、Domain和Path - Console 或 Issues 面板是否提示 Cookie 被阻止
- 支付前后是否发生了 HTTP/HTTPS、
www/非www或其他子域切换
推荐方案:跨站回调完成后再跳转到站内页面
如果第三方支付站点通过 POST 将用户送回 WordPress,回调入口不应依赖浏览器中的 WooCommerce session,而应根据订单号、一次性随机值和支付平台签名独立验证请求。
验证成功后,再使用 303 See Other 跳转到站内页面:
<?php
function handle_payment_return(): void
{
// 示例字段名仅用于说明,实际名称以支付服务商协议为准。
$order_id = isset($_POST['order_id'])
? absint(wp_unslash($_POST['order_id']))
: 0;
$signature = isset($_POST['signature'])
? sanitize_text_field(wp_unslash($_POST['signature']))
: '';
$payload = $_POST;
if (!$order_id || !verify_gateway_signature($payload, $signature)) {
status_header(400);
exit('Invalid payment callback');
}
$order = wc_get_order($order_id);
if (!$order) {
status_header(404);
exit('Order not found');
}
// 支付状态更新必须设计为幂等操作。
// 实际调用应根据支付服务商返回的交易状态决定。
if (!$order->is_paid()) {
$order->payment_complete();
}
$return_url = $order->get_checkout_order_received_url();
wp_safe_redirect($return_url, 303);
exit;
}
需要注意:
- 第一次跨站
POST即使不带 Cookie,也能根据签名和订单数据完成验证。 303会让浏览器使用GET请求访问站内结果页。- 顶层站内
GET通常可以携带SameSite=LaxCookie。 - 支付状态不能只依靠浏览器回跳,最好同时通过支付平台的服务端 webhook 或通知接口确认。
verify_gateway_signature() 必须按照支付服务商的官方签名规则实现,不能只比较订单号、金额或 URL 参数。
使用一次性 state,不要使用 Session ID
如果需要将浏览器回跳与某笔支付关联,可以在发起支付前生成高强度、短期有效且只能使用一次的随机 state。URL 中只传递这个随机值,不要传递 WordPress 登录 Cookie、WooCommerce session ID 或 PHP session ID。
$state = bin2hex(random_bytes(32));
// 数据库存储哈希值,避免数据库泄漏后直接暴露可用 token。
$state_hash = hash('sha256', $state);
store_checkout_state([
'state_hash' => $state_hash,
'order_id' => $order_id,
'expires_at' => time() + 10 * MINUTE_IN_SECONDS,
'used' => false,
]);
$return_url = add_query_arg(
['state' => rawurlencode($state)],
home_url('/payment/return/')
);
回跳时进行验证:
$state = isset($_REQUEST['state'])
? sanitize_text_field(wp_unslash($_REQUEST['state']))
: '';
$record = find_checkout_state(hash('sha256', $state));
if (
!$record ||
$record['used'] ||
$record['expires_at'] < time()
) {
status_header(400);
exit('Invalid or expired state');
}
// 还应验证支付平台签名、订单号、金额和商户信息。
mark_checkout_state_as_used($record);
URL 可能被写入浏览器历史、代理日志和分析系统。因此,即使使用 state,也必须保证它短期有效、不可预测且只能使用一次。它只能关联请求,不能单独证明“用户已经登录”或“支付成功”。
何时使用 SameSite=None
如果支付流程运行在第三方 iframe 中,或者业务必须在跨站 POST 的第一次请求中读取 Cookie,可能需要使用:
SameSite=None; Secure
例如,可以这样设置自定义的关联 Cookie:
setcookie('checkout_correlation', $opaque_token, [
'expires' => time() + 600,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'None',
]);
这个 Cookie 不能替代 WooCommerce session Cookie。不要参照这段代码重写 WordPress 登录 Cookie,也不要假定当前 WooCommerce 版本一定提供某个 Cookie 配置钩子。相关接口会随 WordPress、WooCommerce 及支付插件的版本变化,应以实际安装版本的源码和文档为准。
SameSite=None 允许 Cookie 出现在更多跨站场景中,因此必须满足以下条件:
- 全站使用 HTTPS
- Cookie 同时设置
Secure - 状态变更请求使用 nonce、一次性 state 或同等强度的 CSRF 防护
- 支付回调验证服务商签名
- 校验订单号、金额、币种和商户身份
- 回调处理具有幂等性,可以安全处理重复通知
还需要排查的配置问题
如果 Cookie 在支付前后确实从浏览器存储中消失,而不是仅在跨站请求中没有发送,还要检查:
- WordPress Address 和 Site Address 是否完全一致
- 是否在
http://和https://之间切换 - 是否在
example.com和www.example.com之间切换 - 反向代理是否正确传递 HTTPS 状态
- CDN、缓存插件或安全插件是否改写
Set-Cookie - 回调页面是否被页面缓存
- Cookie 的
Domain和Path是否覆盖回跳地址 - 服务器时间是否正确,Cookie 是否被误判为过期
- WooCommerce、支付网关插件和主题中是否存在过时的自定义 session 逻辑
最稳妥的设计是让支付确认完全不依赖浏览器 session。服务端回调负责更新订单,浏览器回跳只展示结果。这样,即使 Chrome 因 SameSite 策略没有在第一次请求中发送 Cookie,也不会导致订单状态错误,更不需要暴露 session ID。
备注:内容仅供参考。
