slim/csrf

Slim 框架 4 CSRF 保护 PSR-15 中间件

1.5.0 2024-06-08 16:37 UTC

This package is auto-updated.

Last update: 2024-09-09 13:48:11 UTC


README

Build Status Coverage Status

此存储库包含一个用于 Slim 框架 CSRF 保护 PSR-15 中间件。CSRF 保护适用于所有不安全的 HTTP 请求(POST、PUT、DELETE、PATCH)。

您可以使用其 getAttribute() 方法从请求对象中获取最新的 CSRF 令牌名称和值。默认情况下,CSRF 令牌的名称存储在 csrf_name 属性中,CSRF 令牌的值存储在 csrf_value 属性中。

安装

通过 Composer

$ composer require slim/csrf

需要 Slim 4.0.0 或更高版本。

使用方法

在大多数情况下,您希望为所有路由注册 Slim\Csrf,但是,由于它是中间件,您也可以为路由子集注册它。

为所有路由注册

use DI\Container;
use Slim\Csrf\Guard;
use Slim\Factory\AppFactory;

require __DIR__ . '/vendor/autoload.php';

// Start PHP session
session_start();

// Create Container
$container = new Container();
AppFactory::setContainer($container);

// Create App
$app = AppFactory::create();
$responseFactory = $app->getResponseFactory();

// Register Middleware On Container
$container->set('csrf', function () use ($responseFactory) {
    return new Guard($responseFactory);
});

// Register Middleware To Be Executed On All Routes
$app->add('csrf');

$app->get('/foo', function ($request, $response, $args) {
    // CSRF token name and value
    $csrf = $this->get('csrf');
    $nameKey = $csrf->getTokenNameKey();
    $valueKey = $csrf->getTokenValueKey();
    $name = $request->getAttribute($nameKey);
    $value = $request->getAttribute($valueKey);

    /*
       Render HTML form which POSTs to /bar with two hidden input fields for the
       name and value:
       <input type="hidden" name="<?= $nameKey ?>" value="<?= $name ?>">
       <input type="hidden" name="<?= $valueKey ?>" value="<?= $value ?>">
     */
});

$app->post('/bar', function ($request, $response, $args) {
    // CSRF protection successful if you reached
    // this far.
});

$app->run();

按路由注册

use DI\Container;
use Slim\Csrf\Guard;
use Slim\Factory\AppFactory;

require __DIR__ . '/vendor/autoload.php';

// Start PHP session
session_start();

// Create Container
$container = new Container();
AppFactory::setContainer($container);

// Create App
$app = AppFactory::create();
$responseFactory = $app->getResponseFactory();

// Register Middleware On Container
$container->set('csrf', function () use ($responseFactory) {
    return new Guard($responseFactory);
});

$app->get('/api/route',function ($request, $response, $args) {
    $csrf = $this->get('csrf');
    $nameKey = $csrf->getTokenNameKey();
    $valueKey = $csrf->getTokenValueKey();
    $name = $request->getAttribute($nameKey);
    $value = $request->getAttribute($valueKey);

    $tokenArray = [
        $nameKey => $name,
        $valueKey => $value
    ];
    
    return $response->write(json_encode($tokenArray));
})->add('csrf');

$app->post('/api/myEndPoint',function ($request, $response, $args) {
    //Do my Things Securely!
})->add('csrf');

$app->run();

手动使用

如果您愿意在 Slim\App 外部或不以中间件的形式使用 Slim\Csrf\Guard,请务必验证存储。

use Slim\Csrf\Guard;
use Slim\Psr7\Factory\ResponseFactory;

// Start PHP session
session_start();

// Create Middleware
$responseFactory = new ResponseFactory(); // Note that you will need to import
$guard = new Guard($responseFactory);

// Generate new tokens
$csrfNameKey = $guard->getTokenNameKey();
$csrfValueKey = $guard->getTokenValueKey();
$keyPair = $guard->generateToken();

// Validate retrieved tokens
$guard->validateToken($_POST[$csrfNameKey], $_POST[$csrfValueKey]);

令牌持久性

默认情况下,Slim\Csrf\Guard 将在每个请求后生成一个新的名称/值对。这对于 某些情况 是一项重要的安全措施。然而,在许多情况下,这是不必要的,并且 整个会话中只有一个令牌就足够了。通过使用会话请求,例如,处理 AJAX 请求变得更加容易,无需在每次请求后重新加载页面或发送单独的请求来检索新的 CSRF 令牌。参见问题 #49

要使用持久令牌,将构造函数的第六个参数设置为 true。无论何种情况,令牌将在 CSRF 检查失败后重新生成。在这种情况下,您可能希望检测此条件并指示您的用户在他们的合法浏览器标签页中重新加载页面(或在下一个失败请求时自动重新加载)。

在模板(Twig 等)中访问令牌对

在许多情况下,您可能希望在不通过请求对象的情况下访问令牌对。在这种情况下,您可以直接在 Guard 中间件实例上使用 getTokenName()getTokenValue()。这可以非常有用,例如在 Twig 扩展 中。

use Slim\Csrf\Guard;

class CsrfExtension extends \Twig\Extension\AbstractExtension implements \Twig\Extension\GlobalsInterface
{
    /**
     * @var Guard
     */
    protected $csrf;
    
    public function __construct(Guard $csrf)
    {
        $this->csrf = $csrf;
    }

    public function getGlobals()
    {
        // CSRF token name and value
        $csrfNameKey = $this->csrf->getTokenNameKey();
        $csrfValueKey = $this->csrf->getTokenValueKey();
        $csrfName = $this->csrf->getTokenName();
        $csrfValue = $this->csrf->getTokenValue();
        
        return [
            'csrf'   => [
                'keys' => [
                    'name'  => $csrfNameKey,
                    'value' => $csrfValueKey
                ],
                'name'  => $csrfName,
                'value' => $csrfValue
            ]
        ];
    }
}

注册您的扩展后,您可以在任何模板中访问令牌对。

<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">

处理验证失败

默认情况下,Slim\Csrf\Guard 将返回一个状态码为 400 的响应和一个简单的纯文本错误消息。

要覆盖此行为,提供可调用的函数作为构造函数的第三个参数或通过 setFailureHandler()。此可调用的签名与中间件相同:function($request, $handler),必须返回一个响应。

例如

use Slim\Csrf\Guard;
use Slim\Psr7\Factory\ResponseFactory;

$responseFactory = new ResponseFactory();
$guard = new Guard($responseFactory);
$guard->setFailureHandler(function (ServerRequestInterface $request, RequestHandlerInterface $handler) {
    $request = $request->withAttribute("csrf_status", false);
    return $handler->handle($request);
});

在此示例中,请求对象上设置了一个属性,然后可以在后续的中间件或路由可调用中使用该属性进行验证。

if (false === $request->getAttribute('csrf_status')) {
    // display suitable error here
} else {
    // successfully passed CSRF check
}

测试

$ phpunit

贡献

请参阅 CONTRIBUTING 以获取详细信息。

安全

如果您发现任何与安全相关的问题,请通过电子邮件 [email protected] 联系,而不是使用问题跟踪器。

鸣谢

许可证

MIT许可证(MIT)。请参阅 许可证文件 以获取更多信息。