CSRF Token 双提交 Cookie 模式
解读
国内一线互联网公司在面试 PHP 后端岗位时,常把 CSRF 防护作为“安全必考题”。双提交 Cookie 模式(Double Submit Cookie)是除同步器令牌(Synchronizer Token)外最常被落地的方案,尤其适合分布式、无状态、前后端分离的 PHP 项目。面试官问此题,核心想验证三点:
- 是否理解 CSRF 攻击本质——“借用浏览器自动携带的 Cookie 完成状态写操作”;
- 是否明白双提交 Cookie 为什么“不需要服务端会话存储”,从而天然支持水平扩展;
- 能否用 PHP 细节(setcookie 参数、SameSite、加密、HTTPS 防嗅探)把方案讲透,并指出国内常见坑(小程序 WebView、HTTPS 证书链不全、APP 内嵌 H5 跨域等)。
知识点
- CSRF 攻击原理:第三方站点冒用已登录用户的 Cookie 发起写请求。
- 双提交 Cookie 核心:
a. 登陆后服务端把“随机令牌”写入 Cookie,并同时在响应体(或后续 AJAX 接口)返回同一令牌;
b. 后续写请求必须把该令牌通过自定义头或表单字段再带回来;
c. 服务端比对“请求参数中的令牌”与 Cookie 中的令牌”,值相同即放行;
d. 由于第三方域无法读取或写入目标域 Cookie,也无法构造出与 Cookie 值相同的参数,因此伪造请求失败。 - PHP 关键技术点:
- 使用
random_bytes(32)生成加密安全随机数,再用bin2hex或base64_encode转字符串; setcookie('csrf_token', $token, ['httponly'=>false, 'samesite'=>'Strict', 'secure'=>true, 'path'=>'/']);- 前端取 Cookie 时,如跨域需
withCredentials=true;如用 Authorization 头则无需 Cookie 模式; - 校验阶段:
hash_equals($_COOKIE['csrf_token'], $_SERVER['HTTP_X_CSRF_TOKEN']); - 需全局中间件统一拦截,避免开发者漏写校验。
- 使用
- 国内合规与性能:
- 《网络安全法》要求“采取防范危害网络安全行为的技术措施”,双提交 Cookie 属于其中一种,但需配合 HTTPS、CSP、SRI 等综合防护;
- 高并发场景下,该模式零会话、零 IO,适合 PHP-FPM + Nginx 多机部署,OPcache 无需额外缓存。
答案
“双提交 Cookie 模式”是指:
- 用户登录成功后,PHP 生成一个 32 字节随机令牌
$token = bin2hex(random_bytes(32)); - 服务端立即通过
setcookie把令牌种到浏览器,属性必须包含Secure; SameSite=Strict,HttpOnly置为false(因为前端要读取); - 同时把同一令牌返回给前端,可放在 JSON 响应体,也可放在页面
<meta name="csrf-token" content="{$token}">; - 后续所有写请求(POST/PUT/DELETE),前端通过
X-CSRF-Token头或_csrf字段把令牌带回; - PHP 入口文件或 Laravel/Symfony 中间件里统一校验:
$cookieToken = $_COOKIE['csrf_token'] ?? ''; $headerToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; if (!hash_equals($cookieToken, $headerToken)) { http_response_code(403); exit('CSRF validation failed'); } - 退出登录或令牌过期时,把 Cookie 置空并设置
expires=time()-3600,强制浏览器删除。
该方案优点:无状态、无 Session、无 Redis,PHP 服务器可任意水平扩展;缺点:依赖 HTTPS 防中间人篡改 Cookie,且前端必须能访问 Cookie,对小程序 WebView 需开 sameSite=None; Secure 并做额外降级。
拓展思考
- 国内小程序生态下,WebView 默认禁止第三方 Cookie,双提交 Cookie 会失效。此时可降级为“Authorization 头 + 短时间 JWT”模式,或采用“登录后下发令牌到本地存储,再带回头”的变体。
- 如果站点存在 XSS,攻击者就能读 Cookie 并构造合法请求,双提交 Cookie 随之失效。因此必须配套 CSP、输入过滤、输出转义,才能构成完整防线。
- 大型 PHP 集群若想进一步降低风险,可“加密 Cookie”:服务端用
openssl_encrypt($token, 'AES-256-GCM', $key, 0, $iv, $tag)把令牌加密后再写入 Cookie,前端仍明文读取,但中间人即使拿到 Cookie 也无法篡改密文,解密失败直接 403。 - 面试加分项:能说出“SameSite 属性在 2020 年后被 Chrome 默认启用,对 CSRF 有一定缓解,但仍需双提交做纵深防御”;并能给出“灰度发布时如何兼容旧版本 APP 内嵌 H5”的具体代码——例如通过 User-Agent 识别旧版,动态关闭
SameSite=Strict,同时缩短令牌有效期并加入 IP、UA 绑定。