ThinkPHP 內建表單令牌驗證 (防 CSRF),開啟 TOKEN_ON 後,若提交報錯 "表單令牌錯誤",常見原因有三類:**字段快取檔案遺失/損壞、令牌驗證被錯誤關閉導致資料空值、模板缺少令牌隱藏域**。本文逐一剖析並給出修復步驟。
// 控制器 create() 方法中
$model = M('User');
if ($model->create()) { // 內部自動驗證令牌
$model->add();
} else {
$this->error($model->getError()); // 輸出: "表單令牌錯誤"
}
ThinkPHP 為避免每次查詢 SHOW COLUMNS,會將資料表結構快取至 Runtime/Data/_fields/ 目錄下 (每表一個 PHP 回傳陣列檔)。模型 create() 依賴此快取判斷哪些欄位合法、哪些是主鍵/自增。若快取檔案遺失、被誤刪、或內容被清空 (僅剩 '_autoinc' => true),模型會認為**所有傳入欄位皆非法**,導致 create() 回傳 false,錯誤訊息卻誤導為 "表單令牌錯誤"。
// Runtime/Data/_fields/user.php
<?php return array(
0 => 'id',
1 => 'username',
2 => 'password',
3 => 'email',
'_autoinc' => true,
'_pk' => 'id',
); ?>
<?php return array(
'_autoinc' => true,
); ?>
Runtime/Data/_fields/ 目錄。user.php)。Runtime/ 目錄寫入權限 (Linux: chmod -R 775 Runtime/、擁有者 www-data)。部分開發者為「快速解決」在配置中設 'TOKEN_ON' => false 或模板加 {__NOTOKEN__}。雖不再報錯,但 create() 因無字段快取 (見原因 1) 仍**無法自動過濾合法欄位**,導致 $model->data 為空,**寫入資料庫的全是 NULL/預設值**。
正確做法:修復字段快取 (原因 1) 後,**保持令牌驗證開啟** (預設即開啟),安全與功能兼得。
若字段快取正常,但模板表單**未使用 ThinkPHP 表單標籤** 或 **手寫 HTML 未包含令牌隱藏域**,則真實觸發令牌驗證失敗。
<form action="__URL__/insert" method="post">
<input type="text" name="username" />
<input type="password" name="password" />
<!-- ThinkPHP 表單標籤自動生成令牌隱藏域 -->
<!-- 或手寫: -->
<input type="hidden" name="__TOKEN__" value="{:session('__TOKEN__')}" />
<input type="submit" value="提交" />
</form>
或使用內建標籤 (自動注入令牌):
<form action="__URL__/insert" method="post">
<input type="text" name="username" />
<input type="password" name="password" />
{:token()} <!-- 模板標籤輸出隱藏域 -->
<input type="submit" value="提交" />
</form>
Runtime/Cache/, Runtime/Data/_fields/ 相關檔,重試。Runtime/ 目錄 Web 使用者可讀寫。{:token()} 或 <input name="__TOKEN__">。'TOKEN_ON' => true (預設),'TOKEN_NAME' => '__TOKEN__' (預設)。TOKEN_TYPE 設為 session (預設) 或改用 cookie,或前端提交前刷新令牌 (AJAX 取新令牌)。90% 以上的 "表單令牌錯誤" 實為字段快取遺失導致的誤報。遇到此錯誤,**首要動作是刪除 Runtime/Data/_fields/ 下對應模型快取檔**,而非關閉令牌驗證。保持令牌開啟、快取完整、模板正確,即可同時擁有 CSRF 防護與正常表單提交。