thinkphp 6.0 表单提交突然 403 是因 csrf 令牌过期或不匹配,非权限问题而是中间件拦截;默认 token 存于 session、有效期 3600 秒,页面刷新即更新 token,旧页提交旧值或 ajax 未正确传 token 均触发 403。

为什么表单提交突然 403?CSRF 令牌过期或不匹配
ThinkPHP 6.0 默认开启 CSRF 防护,form 提交时若 _token 缺失、过期或与 session 中的不一致,就会返回 403 Forbidden。这不是权限问题,而是中间件拦截了非法请求。常见于页面长时间未操作后提交、AJAX 重复提交、或页面缓存了旧 token。
- TP6 的 CSRF token 存在 session 中,有效期默认 3600 秒(可配
csrf.expire) - 每次刷新页面,
token()函数会生成新 token 并更新 session;但旧页面里的 hidden input 仍用旧值 - 如果用户开多个标签页共用同一 session,其中一个页刷新了 token,其他页再提交就会 403
如何安全刷新表单中的 CSRF token?
不能只靠 JS 定时重拉,也不能每次提交都先 GET 一次 token 接口——这破坏幂等性且增加并发风险。正确做法是:在表单渲染时生成 token,并在关键交互(如弹窗打开、tab 切换)后主动更新 DOM 中的 _token 值。
- 服务端用
token()输出初始值:<input type="hidden" name="_token" value="<?= token() ?>">
-
前端 JS 可封装一个刷新函数,调用
/api/csrf-token(需路由允许 GET 且不校验 CSRF)返回新 token:fetch('/api/csrf-token').then(r => r.json()).then(data => { document.querySelector('[name=_token]').value = data.token; }); - 该接口只需简单返回
['token' => token()],无需额外验证,但必须加白名单路由(见下节)
怎么给特定接口/路由关闭 CSRF 校验?
TP6 的 CSRF 中间件(think\middleware\CheckRequestCache 和 think\middleware\ValidateToken)默认全局启用。要跳过某接口,不能删中间件,而应在路由定义中显式关闭。
- 在
route/app.php中,对需要放行的路由使用->middleware('csrf:false'):Route::post('upload', 'Index/upload')->middleware('csrf:false'); - 注意:不是
->withoutMiddleware('csrf'),TP6 不支持这种写法;csrf:false是中间件参数,由框架识别 - 白名单仅适用于「不需要用户态身份凭证」的接口,比如上传回调、Webhook 接收,切勿对修改数据的普通表单接口关闭
为什么 AJAX 提交也报 403?header 和 token 都得对上
表单 submit 自动带 _token 字段,但 AJAX 默认不附带。即使你手动加了 _token 字段,如果同时设置了 Content-Type: application/json,TP6 的 ValidateToken 中间件会跳过解析 POST 数据(因为 JSON 不走 $_POST),导致 token 检查失败。
- 方案一(推荐):保持
Content-Type: application/x-www-form-urlencoded,把 token 放在 body:fetch('/submit', { method: 'POST', body: new URLSearchParams({ title: 'x', _token: document.querySelector('[name=_token]').value }) }); - 方案二:改服务端逻辑,在中间件里手动解析 JSON body(不推荐,破坏框架约定)
- 方案三:用
X-CSRF-TOKEN请求头(需前端读取 meta 或自定义 header,后端需额外适配,TP6 默认不支持)
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











