根本原因是CSRF token传错位置、时机错误或被重复生成;每次访问$app->request->csrfToken都会强制刷新token,应改用getCsrfToken()获取会话绑定的原始值,并通过X-CSRF-Token请求头传递。

Yii 默认已启用 CSRF 防护,但 400 错误频繁出现,根本原因不是“没开”,而是 token 传错了位置、用错了时机,或被重复生成覆盖了原始值。
CSRF Token 为什么不能直接用 csrfToken 属性?
每次访问 $app->request->csrfToken 都会生成一个新 token —— 它不是读取会话里那个“原始值”,而是强制刷新。页面中表单隐藏字段、<meta name="csrf-param"> 和 <meta name="csrf-token"> 用的都是首次加载时生成的那个 token;而 JS 里若写 'X-CSRF-Token': '= $app->request->csrfToken ?>',就等于拿了个新 token 去校验旧 session,必然失败。
- 正确做法是统一用
getCsrfToken():它只读不生成,返回当前会话绑定的 token 值 - 视图中应写作:
const CSRF_TOKEN = "= Yii::$app->request->getCsrfToken() ?>"; - 避免在多个地方反复调用
getCsrfToken()渲染——只要一次就够了,后续 JS 全部复用这个变量
AJAX POST 提交 JSON 数据时怎么带 token?
表单自动注入的 _csrf 字段对 application/json 请求无效,因为 body 是纯 JSON,没有字段名可塞。此时必须走请求头,且 header 名必须严格为 X-CSRF-Token(大小写敏感)。
- fetch 示例:
fetch('/order/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': CSRF_TOKEN }, body: JSON.stringify({ amount: 100 }) }); - 如果用了 jQuery,别用
$.ajaxSetup全局设 header —— 它会在每次请求前重新求值,容易触发重复生成问题;改用beforeSend或每个请求单独配 - 注意:Laravel 等框架常用
X-XSRF-TOKEN,Yii 不认这个,只认X-CSRF-Token
SameSite Cookie 怎么配才不打断登录跳转?
CSRF token 是会话级的,依赖 cookie 传递 session ID;如果 SameSite 设成 Strict,用户从微信、邮件链接进站时 cookie 不发送,session 丢失,CSRF token 就对不上了。
- PHP 层设置最稳:
ini_set('session.cookie_samesite', 'Lax');放在web/index.php开头 - 如果用了
yii\web\Session自定义配置,必须同步加:'cookieParams' => ['sameSite' => 'Lax'] - 别指望 Nginx 的
add_header Set-Cookie覆盖 session cookie —— Yii 自己 setcookie 时不会合并,反而可能被忽略
最常被忽略的一点:CSRF 校验失败时,Yii 不会告诉你“token 过期”,只会抛 HttpException: 400。但真实原因往往只是前后端 token 来源不一致 —— 你得盯住 session 中存的是哪个值、前端发的是哪个值、中间有没有被 JS 无意重生成过。


















