本文目录导读:

在ThinkPHP框架中实现CSRF令牌验证主要涉及以下几个方面:
基础配置
在config目录下的配置文件中启用CSRF:
// config/middleware.php 或 config/app.php
return [
// 是否开启CSRF验证
'csrf_verify' => true,
// CSRF令牌字段名
'token_name' => '__token__',
// CSRF令牌存储方式
'token_storage' => 'session', // session 或 cookie
];
中间件实现
创建CSRF验证中间件:
// app/middleware/CSRF.php
namespace app\middleware;
use think\exception\HttpException;
use think\Response;
use think\facade\Session;
use think\facade\Cookie;
class CSRF
{
public function handle($request, \Closure $next)
{
// 验证CSRF令牌
if (!$this->checkToken($request)) {
if ($request->isAjax()) {
return json(['code' => 401, 'msg' => 'CSRF验证失败']);
}
throw new HttpException(403, 'CSRF验证失败');
}
return $next($request);
}
protected function checkToken($request)
{
// 排除GET和HEAD请求
if ($request->isGet() || $request->isHead()) {
return true;
}
// 验证令牌
$token = $request->post('__token__') ?: $request->header('X-CSRF-Token');
$sessionToken = Session::get('csrf_token');
return $token === $sessionToken;
}
protected function generateToken()
{
return md5(uniqid(mt_rand(), true));
}
}
路由和控制器使用
在路由中启用CSRF:
// route/app.php
use think\facade\Route;
// 在路由组中应用中间件
Route::group('user', function () {
Route::post('save', 'User/save');
Route::post('delete', 'User/delete');
})->middleware(\app\middleware\CSRF::class);
// 或单独路由
Route::post('user/save', 'User/save')->middleware(\app\middleware\CSRF::class);
在控制器中使用:
// app/controller/User.php
namespace app\controller;
use think\Request;
use think\facade\Session;
class User
{
public function save(Request $request)
{
// 手动验证CSRF(可选,如果使用了中间件则不需要)
if (!$this->checkToken($request)) {
return json(['code' => 401, 'msg' => 'CSRF验证失败']);
}
// 处理业务逻辑
return json(['code' => 200, 'msg' => '保存成功']);
}
protected function checkToken(Request $request)
{
$token = $request->param('__token__');
$sessionToken = Session::get('csrf_token');
return $token === $sessionToken;
}
public function getToken()
{
$token = md5(uniqid(mt_rand(), true));
Session::set('csrf_token', $token);
return $token;
}
}
模板中的使用方法
在HTML模板中添加CSRF令牌:
<!-- 表单中 -->
<form method="POST" action="/user/save">
<input type="hidden" name="__token__" value="{$Think.SESSION.csrf_token}" />
<!-- 或者使用封装的函数 -->
{csrf_field()}
<input type="text" name="name" />
<button type="submit">提交</button>
</form>
<!-- AJAX请求中 -->
<script>
// 获取CSRF令牌
let csrfToken = '{$Think.SESSION.csrf_token}';
// 使用fetch
fetch('/api/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({data: 'test'})
});
// 使用jQuery
$.ajax({
url: '/api/update',
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken
},
data: {data: 'test'}
});
</script>
完整的控制器示例
// app/controller/CSRFToken.php
namespace app\controller;
use think\facade\Session;
use think\response\Json;
class CSRFToken
{
public function getToken(): Json
{
$token = $this->generateToken();
Session::set('csrf_token', $token);
return json([
'code' => 200,
'token' => $token
]);
}
public function refreshToken(): Json
{
$token = $this->generateToken();
Session::set('csrf_token', $token);
return json([
'code' => 200,
'token' => $token
]);
}
protected function generateToken(): string
{
return md5(uniqid(mt_rand(), true) . microtime());
}
public function validate(Request $request): Json
{
$token = $request->param('__token__');
$sessionToken = Session::get('csrf_token');
if ($token === $sessionToken) {
return json(['code' => 200, 'status' => 'valid']);
}
return json(['code' => 401, 'status' => 'invalid']);
}
}
全局CSRF验证(使用全局中间件)
// app/middleware.php
return [
\app\middleware\CSRF::class
];
异常处理
// app/ExceptionHandle.php
namespace app;
use think\db\exception\DataNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\response\Json;
class ExceptionHandle extends Handle
{
public function render($request, \Throwable $e)
{
// 处理CSRF异常
if ($e instanceof HttpException && $e->getStatusCode() === 403) {
if ($request->isAjax()) {
return json(['code' => 403, 'msg' => 'CSRF验证失败,请刷新页面重试']);
}
return response('CSRF验证失败', 403);
}
return parent::render($request, $e);
}
}
注意事项
- GET请求 - 通常情况下GET请求不需要验证(但某些敏感操作除外)
- Token有效期 - 可以设置令牌有效期,过期后需要重新获取
- 多表单场景 - 每个表单都需要包含CSRF令牌
- AJAX请求 - 需要在请求头中添加CSRF令牌
- 登录状态变化 - 用户登录状态变化时应重新生成CSRF令牌
使用CSRF令牌验证能有效防止跨站请求伪造攻击,保护用户数据安全。