跨站请求伪造 (CSRF) 是一种攻击类型,攻击者可以在不知情或未经授权的情况下,利用经过身份验证的用户的身份执行命令。
CakePHP 提供了两种形式的 CSRF 防护
SessionCsrfProtectionMiddleware
将 CSRF 令牌存储在会话中。这需要您的应用程序在每次请求时打开会话,这可能会产生副作用。基于会话的 CSRF 令牌的优点是,它们的作用域限定为特定用户,并且只在会话处于活动状态时有效。
CsrfProtectionMiddleware
将 CSRF 令牌存储在 Cookie 中。使用 Cookie 可以使 CSRF 检查在服务器上没有任何状态的情况下完成。Cookie 值使用 HMAC 检查进行验证。但是,由于其无状态的性质,CSRF 令牌在不同用户和会话之间是可重复使用的。
注意
您不能同时使用以下两种方法,必须选择其中一种。如果您同时使用两种方法,则每次 PUT 和 POST 请求都会出现 CSRF 令牌不匹配错误。
CSRF 防护可以应用于您的整个应用程序,也可以应用于特定路由作用域。通过将 CSRF 中间件应用于您的应用程序中间件堆栈,您可以保护应用程序中的所有操作。
// in src/Application.php
// For Cookie based CSRF tokens.
use Cake\Http\Middleware\CsrfProtectionMiddleware;
// For Session based CSRF tokens.
use Cake\Http\Middleware\SessionCsrfProtectionMiddleware;
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$options = [
// ...
];
$csrf = new CsrfProtectionMiddleware($options);
// or
$csrf = new SessionCsrfProtectionMiddleware($options);
$middlewareQueue->add($csrf);
return $middlewareQueue;
}
通过将 CSRF 防护应用于路由作用域,您可以有条件地将 CSRF 应用于特定路由组。
// in src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Routing\RouteBuilder;
public function routes(RouteBuilder $routes) : void
{
$options = [
// ...
];
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
parent::routes($routes);
}
// in config/routes.php
$routes->scope('/', function (RouteBuilder $routes) {
$routes->applyMiddleware('csrf');
});
可用的配置选项有
key
要使用的会话键。默认为 csrfToken
field
要检查的表单字段。更改此项还需要配置 FormHelper。
启用后,您可以从请求对象访问当前 CSRF 令牌
$token = $this->request->getAttribute('csrfToken');
如果您需要旋转或替换会话 CSRF 令牌,您可以使用以下方法
$this->request = SessionCsrfProtectionMiddleware::replaceToken($this->request);
Added in version 4.3.0: 添加了 replaceToken
方法。
两种 CSRF 中间件实现都允许您使用跳过检查回调功能,以便更细粒度地控制哪些 URL 应该执行 CSRF 令牌检查。
// in src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$csrf = new CsrfProtectionMiddleware();
// Token check will be skipped when callback returns `true`.
$csrf->skipCheckCallback(function ($request) {
// Skip token check for API URLs.
if ($request->getParam('prefix') === 'Api') {
return true;
}
});
// Ensure routing middleware is added to the queue before CSRF protection middleware.
$middlewareQueue->add($csrf);
return $middlewareQueue;
}
注意
您应该只对使用 Cookie/会话处理有状态请求的路由应用 CSRF 防护中间件。例如,在开发 API 时,不使用 Cookie 进行身份验证的无状态请求不受 CSRF 的影响,因此不需要为这些路由应用中间件。
CsrfProtectionMiddleware
与 FormHelper
无缝集成。每次您使用 FormHelper
创建表单时,它都会插入一个包含 CSRF 令牌的隐藏字段。
注意
使用 CSRF 防护时,您应该始终使用 FormHelper
启动您的表单。如果不这样做,您需要在每个表单中手动创建隐藏输入。
除了请求数据参数之外,CSRF 令牌还可以通过特殊的 X-CSRF-Token
标头提交。使用标头通常可以更容易地将 CSRF 令牌与 JavaScript 密集型应用程序或基于 XML/JSON 的 API 端点集成。
CSRF 令牌可以通过 JavaScript 的 Cookie csrfToken
或 PHP 的请求对象属性 csrfToken
获取。当您的 JavaScript 代码驻留在与 CakePHP 视图模板分离的文件中时,以及当您已经具有通过 JavaScript 解析 Cookie 的功能时,使用 Cookie 可能更容易。
如果您有单独的 JavaScript 文件,但不想处理 Cookie,您可以例如在布局中将令牌设置为全局 JavaScript 变量,方法是定义一个类似这样的脚本块
echo $this->Html->scriptBlock(sprintf(
'var csrfToken = %s;',
json_encode($this->request->getAttribute('csrfToken'))
));
然后,您可以在此脚本块之后加载的任何脚本文件中以 csrfToken
或 window.csrfToken
的形式访问令牌。
另一个选择是将令牌放在一个自定义的元标记中,例如
echo $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken'));
然后,可以通过查找名为 csrfToken
的 meta
元素,在脚本中访问它,使用 jQuery 可以像这样简单地实现
var csrfToken = $('meta[name="csrfToken"]').attr('content');