ThinkPHP 內置的表單令牌驗證機制能有效防止 CSRF 攻擊、重複提交及遠程表單偽造,是 Web 應用安全的重要防線。
'TOKEN_ON' => true, // 啟用令牌驗證 'TOKEN_NAME' => '__hash__', // 隱藏域名稱 'TOKEN_TYPE' => 'md5', // 哈希算法(默認 MD5)
開啟 TOKEN_ON 後,系統自動在 </form> 前插入隱藏域:
在表單中添加 {__TOKEN__} 標識,系統輸出時自動替換:
在表單頁面添加 {__NOTOKEN__},系統忽略該表單驗證。
建議每個需驗證的表單手動添加 {__TOKEN__},確保只有一個表單啟用驗證。
使用 create() 方法時自動驗證:
$User = M("User");
if ($User->create()) {
// 驗證通過,繼續業務邏輯
$User->add();
} else {
// 驗證失敗,$User->getError() 獲取錯誤信息
}
$User = M("User");
if (!$User->autoCheckToken($_POST)) {
// 令牌驗證失敗處理
$this->error('表單令牌錯誤,請重新提交');
}
protected function templateContentReplace($content) {
$replace = array(
'__PUBLIC__' => WEB_PUBLIC_PATH,
'__TMPL__' => APP_TMPL_PATH,
// ... 其他替換
);
if (C('TOKEN_ON')) {
if (strpos($content, '{__TOKEN__}')) {
// 手動指定位置
$replace['{__TOKEN__}'] = $this->buildFormToken();
} elseif (strpos($content, '{__NOTOKEN__}')) {
// 禁用驗證
$replace['{__NOTOKEN__}'] = '';
} elseif (preg_match('/<\/form(\s*)>/is', $content, $match)) {
// 自動在 前插入
$replace[$match[0]] = $this->buildFormToken() . $match[0];
}
}
// ... 字符串替換返回
}
// 生成令牌並存入 Session
public function buildFormToken() {
$name = C('TOKEN_NAME');
$type = C('TOKEN_TYPE');
$token = $type ? md5(uniqid(rand(), true)) : uniqid(rand(), true);
$_SESSION[$name] = $token;
return '';
}
// 模型驗證邏輯
public function autoCheckToken($data) {
$name = C('TOKEN_NAME');
if (isset($_SESSION[$name])) {
if (empty($data[$name]) || $_SESSION[$name] != $data[$name]) {
return false; // 非法提交
}
unset($_SESSION[$name]); // 驗證後銷毀,防止重複提交
}
return true;
}
| 問題 | 原因 | 解決 |
|---|---|---|
| AJAX 提交失敗 | 未傳遞令牌字段 | 在 AJAX 數據中包含 __hash__ |
| 多標籤頁失效 | Session 令牌被覆蓋 | 使用不同 TOKEN_NAME 或改用 JWT |
| 文件上傳失效 | enctype 導致解析異常 | 確保表單 enctype="multipart/form-data" |
TOKEN_ON,關鍵業務表單手動控制位置TOKEN_TYPE 增加破解難度更多安全開發指南:《ThinkPHP常用方法總結》、《PHP安全編程實戰》