SameSite Cookie 兼容性降级

解读

国内业务往往要兼顾“老微信内置浏览器、支付宝内嵌 WebView、各大安卓厂商系统浏览器”三类顽固旧环境,它们对 2019 年之后 IETF 草案里 SameSite=None; Secure 这一对标记的支持参差不齐:

  1. 部分 Chromium 53~65 内核直接把 SameSite=None 当成 Strict 处理;
  2. iOS 9-11 的 UIWebView 不认识 SameSite,导致带标记的 Cookie 被直接丢弃;
  3. 某些网关(阿里 MGS、腾讯 TGW)在 HTTPS 卸载后忘记把 Secure 标记原样透传,结果 SameSite=None; Secure 被浏览器判定为非法。
    一旦写错,用户会出现“登录态频繁丢失、支付回调无法写 Cookie、H5 活动页反复跳转授权”等线上事故。面试官问“兼容性降级”,实质考察:
  • 能否准确识别 User-Agent 中的“问题内核”;
  • 能否在 PHP 层给出“可灰度、可回滚、零业务侵入”的降级策略;
  • 是否理解 Cookie 与 Session 生命周期、CSRF 防护的耦合关系。

知识点

  1. setcookie 函数签名:setcookie(name,name, value, expire,expire, path, domain,domain, secure, httponly)8个参数httponly) 第 8 个参数 options(PHP 7.3+)才支持 array['samesite'];
  2. 在 7.2 及以下需手动拼 header('Set-Cookie: ...');
  3. SameSite 三值含义:Strict=完全禁止跨站发送,Lax=允许顶级导航 GET,None=任意跨站;
  4. 浏览器嗅探:Chromium 51-66 黑名单 + UC/QQ 浏览器特殊 UA;
  5. 安全侧:降级到 Lax 时必须同步调整 CSRF Token 的二次校验逻辑,否则旧浏览器反而更容易被攻击;
  6. 灰度手段:基于 Cookie 的 A/B 版本号或 ConfCenter 配置,实时切换 SameSite 值;
  7. 监控指标:Cookie 写入成功率、鉴权失败率、前端埋点上报“SameSite 异常”事件。

答案

线上 PHP 7.2 环境,封装统一 Cookie 管理类,兼容降级思路如下:

  1. 先定义“问题浏览器”正则:
    broken=pregmatch(/Chrom(eium)\/(5[19]6[06])UCBrowser\/(1[02])˙MiuiBrowser\/(9˙10)˙/i,broken = preg_match('/Chrom(e|ium)\/(5[1-9]|6[0-6])|UCBrowser\/(1[0-2]\.)|MiuiBrowser\/(9\.|10\.)/i', _SERVER['HTTP_USER_AGENT']);
  2. 配置中心读取开关:
    $cfg = \Config::get('cookie.samesite_policy'); // off = 不发送 SameSite,lax = Lax,none = None
  3. 拼装 Set-Cookie 头:
    if (PHP_VERSION_ID >= 70300) {
    options=[expires=>options = ['expires' => expire, 'path' => path,domain=>path, 'domain' => domain,
    'secure' => true, 'httponly' => true, 'samesite' => ''];
    if (cfg === 'none' && !broken) {
    options['samesite'] = 'None'; } elseif (cfg === 'lax') {
    options['samesite'] = 'Lax'; } setcookie(name, value,value, options);
    } else {
    same=;if(same = ''; if (cfg === 'none' && !broken) { same = '; SameSite=None';
    } elseif (cfg === 'lax') { same = '; SameSite=Lax';
    }
    header(sprintf('Set-Cookie: %s=%s; expires=%s; Max-Age=%d; path=%s; domain=%s; secure; httponly%s',
    name,urlencode(name, urlencode(value), gmdate('D, d-M-Y H:i:s T', expire),expire), expire - time(), path,path, domain, $same), false);
    }
  4. 灰度回滚:
    若监控发现某类 UA 的登录失败率突增 >1%,ConfCenter 一键把 samesite_policy 改为 lax 或 off,无需发版;
  5. CSRF 补偿:
    旧浏览器走 Lax 时,POST/PUT 请求必须带双重 Token(Header + Form),后端中间件统一校验。
    通过上述方案,可在 30 分钟内完成全量降级,保证双十一等高并发节点 0 回滚。

拓展思考

  1. 如果业务同时提供 302 短链跳转广告,Lax 级 Cookie 在跨站 POST 场景下仍会被浏览器拦截,是否考虑把关键标识下沉到 URL 加密参数?如何防止参数被篡改?
  2. 在 HTTP/3 全面普及后,QUIC 层 0-RTT 早期数据复用可能再次绕过 SameSite,PHP 端如何与 CDN 配合,对早期数据请求强制二次鉴权?
  3. 国内小程序 web-view 组件即将全面禁止第三方 Cookie,未来 PHP 服务端是否需要把 Session 体系彻底改造成“无 Cookie + JWT + 刷新令牌”?灰度策略如何设计?