Middleware

Os objetos de middleware permitem que você “embrulhe” seu aplicativo em camadas reutilizáveis e composíveis de manipulação de solicitações ou lógica de criação de respostas. Visualmente, seu aplicativo termina no centro e o middleware é envolvido em volta do aplicativo como uma cebola. Aqui, podemos ver um aplicativo agrupado com os middlewares Routes, Assets, Exception Handling e CORS.

../_images/middleware-setup.png

Quando um pedido é tratado pelo seu aplicativo, ele entra no middleware mais externo. Cada middleware pode delegar a solicitação/resposta para a próxima camada ou retornar uma resposta. O retorno de uma resposta impede que as camadas inferiores vejam a solicitação. Um exemplo disso é o plugin AssetMiddleware manipulando uma solicitação de uma imagem de durante o desenvolvimento.

../_images/middleware-request.png

Se nenhum middleware executar uma ação para manipular a solicitação, um controlador será localizado e terá sua ação invocada ou uma exceção será gerada gerando uma página de erro.

O middleware faz parte da nova pilha HTTP no CakePHP que aproveita as interfaces de solicitação e resposta PSR-7. O CakePHP também suporta o padrão PSR-15 para manipuladores de solicitações de servidor, para que você possa usar qualquer middleware compatível com PSR-15 disponível em The Packagist.

Middleware em CakePHP

O CakePHP fornece vários middlewares para lidar com tarefas comuns em aplicativos da web:

  • Cake\Error\Middleware\ErrorHandlerMiddleware intercepta exceções do middleware empacotado e renderiza uma página de erro usando o manipulador de exceção Erros & Exceções.

  • Cake\Routing\AssetMiddleware verifica se a solicitação está se referindo a um tema ou arquivo estático do plug-in, como CSS, JavaScript ou arquivo de imagem armazenado na pasta raiz da web de um plug-in ou na pasta correspondente a um Tema.

  • Cake\Routing\Middleware\RoutingMiddleware usa o Router para analisar a URL recebida e atribuir parâmetros de roteamento à solicitação.

  • Cake\I18n\Middleware\LocaleSelectorMiddleware habilita a troca automática de idioma no cabeçalho Accept-Language enviado pelo navegador.

  • Cake\Http\Middleware\SecurityHeadersMiddleware facilita adicionar cabeçalhos relacionados à segurança como X-Frame-Options às respostas.

  • Cake\Http\Middleware\EncryptedCookieMiddleware oferece a capacidade de manipular cookies criptografados, caso você precise manipular cookies com dados ofuscados.

  • Cake\Http\Middleware\CsrfProtectionMiddleware adiciona proteção CSRF ao seu aplicativo.

  • Cake\Http\Middleware\BodyParserMiddleware permite decodificar JSON, XML e outros corpos de solicitação codificados com base no cabeçalho Content-Type.

  • Cake\Http\Middleware\CspMiddleware simplifica a adição de cabeçalhos de política de segurança de conteúdo ao seu aplicativo.

Usando Middleware

O middleware pode ser aplicado ao seu aplicativo globalmente ou individualmente a escopos de roteamento.

Para aplicar o middleware a todas as solicitações, use o método middleware da sua classe App\Application. O método de gancho middleware do seu aplicativo será chamado no início do processo de solicitação; você pode usar o objeto MiddlewareQueue para anexar o middleware:

namespace App;

use Cake\Http\BaseApplication;
use Cake\Http\MiddlewareQueue;
use Cake\Error\Middleware\ErrorHandlerMiddleware;

class Application extends BaseApplication
{
    public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
    {
        // Vincule o manipulador de erros à fila do middleware.
        $middlwareQueue->add(new ErrorHandlerMiddleware());

        return $middlwareQueue;
    }
}

Além de adicionar ao final do MiddlewareQueue, você pode executar várias operações:

$layer = new \App\Middleware\CustomMiddleware;

// O middleware adicionado será o último da fila.
$middlwareQueue->add($layer);

// O middleware precedido será o primeiro da fila.
$middlwareQueue->prepend($layer);

// Insira em um slot específico. Se o slot estiver
// fora dos limites, ele será adicionado ao final.
$middlwareQueue->insertAt(2, $layer);

// Insira antes de outro middleware.
// Se a classe nomeada não puder ser encontrada,
// uma exceção será gerada.
$middlwareQueue->insertBefore(
    'Cake\Error\Middleware\ErrorHandlerMiddleware',
    $layer
);

// Insira depois de outro middleware.
// Se a classe nomeada não puder ser encontrada, o
// o middleware será adicionado ao final.
$middlwareQueue->insertAfter(
    'Cake\Error\Middleware\ErrorHandlerMiddleware',
    $layer
);

Além de aplicar o middleware a todo o aplicativo, você pode aplicar o middleware a conjuntos específicos de rotas usando Scope Middleware.

Adicionando Middleware a partir de Plugins

Os plug-ins podem usar seu método de gancho middleware para aplicar qualquer middleware que eles tenham à fila de middleware do aplicativo:

// Em plugins/ContactManager/src/Plugin.php
namespace ContactManager;

use Cake\Core\BasePlugin;
use Cake\Http\MiddlewareQueue;
use ContactManager\Middleware\ContactManagerContextMiddleware;

class Plugin extends BasePlugin
{
    public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
    {
        $middlwareQueue->add(new ContactManagerContextMiddleware());

        return $middlwareQueue;
    }
}

Criando um Middleware

O middleware pode ser implementado como funções anônimas (Closures) ou classes que estendem Psr\Http\Server\MiddlewareInterface. Embora os Closures sejam adequados para tarefas menores, eles tornam os testes mais difíceis e podem criar uma classe Application complicada. As classes de middleware no CakePHP têm algumas convenções:

  • Os arquivos de classe Middleware devem ser colocados em ** src/Middleware**. Por exemplo: src/Middleware/CorsMiddleware.php

  • As classes de middleware devem ter o sufixo Middleware. Por exemplo: LinkMiddleware.

  • O Middleware deve implementar Psr\Http\Server\MiddlewareInterface.

O middleware pode retornar uma resposta chamando $handler->handle() ou criando sua própria resposta. Podemos ver as duas opções em nosso middleware simples:

// Em src/Middleware/TrackingCookieMiddleware.php
namespace App\Middleware;

use Cake\Http\Cookie\Cookie;
use Cake\I18n\Time;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\MiddlewareInterface;

class TrackingCookieMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface
    {
        // Chamar $handler->handle() delega o controle para
        // o *próximo* middleware na fila do seu aplicativo.
        $response = $handler->handle($request);

        if (!$request->getCookie('landing_page')) {
            $expiry = new Time('+ 1 year');
            $response = $response->withCookie(new Cookie(
                'landing_page',
                $request->getRequestTarget(),
                $expiry
            ));
        }

        return $response;
    }
}

Agora que criamos um middleware muito simples, vamos anexá-lo ao nosso aplicativo:

// Em src/Application.php
namespace App;

use App\Middleware\TrackingCookieMiddleware;
use Cake\Http\MiddlewareQueue;

class Application
{
    public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
    {
        // Adicione seu middleware simples à fila
        $middlwareQueue->add(new TrackingCookieMiddleware());

        // Adicione um pouco mais de middleware à fila

        return $middlwareQueue;
    }
}

Roteamento de Middleware

O middleware de roteamento é responsável por aplicar as rotas no seu aplicativo e resolver o: plug-in, o controlador e a ação que uma solicitação está pedindo. Ele pode armazenar em cache a coleção de rotas usada no seu aplicativo para aumentar o tempo de inicialização. Para habilitar o cache de rotas em, forneça o cache configuration desejado como um parâmetro:

// Em Application.php
public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
{
    // ...
    $middlwareQueue->add(new RoutingMiddleware($this, 'routing'));
}

O exemplo acima usaria o mecanismo de cache routing para armazenar a coleção de rotas gerada.

Middleware de Cabeçalho de Segurança

A camada Security Headers Middleware facilita a aplicação de cabeçalhos relacionados à segurança em seu aplicativo. Depois de configurado, o middleware pode aplicar os seguintes cabeçalhos às respostas:

  • X-Content-Type-Options

  • X-Download-Options

  • X-Frame-Options

  • X-Permitted-Cross-Domain-Policies

  • Referrer-Policy

Esse middleware é configurado usando uma interface simples antes de ser aplicado à pilha de middleware do seu aplicativo:

use Cake\Http\Middleware\SecurityHeadersMiddleware;

$securityHeaders = new SecurityHeadersMiddleware();
$securityHeaders
    ->setCrossDomainPolicy()
    ->setReferrerPolicy()
    ->setXFrameOptions()
    ->setXssProtection()
    ->noOpen()
    ->noSniff();

$middlwareQueue->add($securityHeaders);

Middleware do Cabeçalho da Política de Segurança de Conteúdo

O CspMiddleware facilita a adição de cabeçalhos referente a política de segurança de conteúdo em seu aplicativo. Antes de usá-lo, você deve instalar o paragonie/csp-builder:

Você pode configurar o middleware usando uma matriz ou passando um objeto CSPBuilder integrado:

use Cake\Http\Middleware\CspMiddleware;

$csp = new CspMiddleware([
    'script-src' => [
        'allow' => [
            'https://www.google-analytics.com',
        ],
        'self' => true,
        'unsafe-inline' => false,
        'unsafe-eval' => false,
    ],
]);

$middlewareQueue->add($csp);

Falsificação de Solicitação entre Sites (CSRF) Middleware

A proteção CSRF pode ser aplicada a todo o aplicativo ou a escopos de roteamento específicos.

Nota

Você não pode usar as duas abordagens a seguir juntas; deve escolher apenas uma. Se você usar as duas abordagens juntas, ocorrerá um erro de incompatibilidade de token CSRF em cada solicitação PUT e` POST`

Ao aplicar o CsrfProtectionMiddleware à pilha de middleware do Aplicativo, você protege todas as ações no aplicativo:

// Em src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;

public function middleware($middlwareQueue) {
    $options = [
        // ...
    ];
    $csrf = new CsrfProtectionMiddleware($options);

    $middlwareQueue->add($csrf);

    return $middlwareQueue;
}

Ao aplicar o CsrfProtectionMiddleware aos escopos de roteamento, você pode incluir ou excluir grupos de rotas específicos:

// Em src/Application.php
use Cake\Http\Middleware\CsrfProtectionMiddleware;

public function routes($routes) {
    $options = [
        // ...
    ];
    $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware($options));
    parent::routes($routes);
}

// Em config/routes.php
Router::scope('/', function (RouteBuilder $routes) {
    $routes->applyMiddleware('csrf');
});

As opções podem ser passadas para o construtor do middleware. As opções de configuração disponíveis são:

  • cookieName O nome do cookie a ser enviado. O padrão é `` csrfToken``.

  • expiry Quanto tempo o token CSRF deve durar. O padrão é a sessão do navegador.

  • secure Se o cookie será ou não definido com o sinalizador Secure. Isso é,   o cookie será definido apenas em uma conexão HTTPS e qualquer tentativa no HTTP normal falhará. O padrão é false.

  • httpOnly Se o cookie será ou não definido com o sinalizador HttpOnly. O padrão é false.

  • field O campo do formulário a ser verificado. O padrão é _csrfToken. Alterar isso também exigirá a configuração do FormHelper.

Quando ativado, você pode acessar o token CSRF atual no objeto de solicitação:

$token = $this->request->getParam('_csrfToken');

Nota

Você deve aplicar o middleware de proteção CSRF apenas para URLs que manipulam solicitações com estado usando cookies/sessão. Solicitações sem estado, por ex. ao desenvolver uma API, não são afetados pelo CSRF; portanto, o middleware não precisa ser aplicado a essas URLs.

Integração com FormHelper

O CsrfProtectionMiddleware se integra perfeitamente ao FormHelper. Cada vez que você cria um formulário com FormHelper, ele insere um campo oculto que contém o token CSRF.

Nota

Ao usar a proteção CSRF, você sempre deve iniciar seus formulários com o FormHelper. Caso contrário, será necessário criar manualmente entradas ocultas em cada um dos seus formulários.

Solicitações de Proteção CSRF e AJAX

Além de solicitar parâmetros de dados, os tokens CSRF podem ser enviados por meio de um cabeçalho especial X-CSRF-Token. O uso de um cabeçalho geralmente facilita a integração de um token CSRF com aplicativos pesados de JavaScript ou endpoints de API baseados em XML/JSON.

O token CSRF pode ser obtido através do cookie csrfToken.

Body Parser Middleware

Se seu aplicativo aceitar JSON, XML ou outros corpos de solicitação codificados, o BodyParserMiddleware permitirá que você decodifique essas solicitações em uma matriz que esteja disponível em $request->getParsedData() e $request->getData(). Por padrão, apenas os corpos json serão analisados, mas a análise XML pode ser ativada com uma opção. Você também pode definir seus próprios analisadores:

use Cake\Http\Middleware\BodyParserMiddleware;

// somente JSON será analisado.
$bodies = new BodyParserMiddleware();

// Ativar análise XML
$bodies = new BodyParserMiddleware(['xml' => true]);

// Desativar a análise JSON
$bodies = new BodyParserMiddleware(['json' => false]);

// Adicione seu próprio analisador que corresponda aos
// valores do cabeçalho do tipo de conteúdo à chamada que pode analisá-los.
$bodies = new BodyParserMiddleware();
$bodies->addParser(['text/csv'], function ($body, $request) {
    // Use uma biblioteca de análise CSV.
    return Csv::parse($body);
});