symfony Разрешить HTTP OPTIONS для набора (или всех) методов (без бандла). Обработка события получения запроса kernel.request

Проверено в Symfony 4.3:

Предлагаемое решение состоит в том, чтобы поставить подобный обработчик события, в дикторию:

# src/EventListener/RequestListener.php
namespace App\EventListener;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use App\Security\CorsSettings;
use Symfony\Component\HttpFoundation\JsonResponse;

class RequestListener
{
    public function onKernelRequest(GetResponseEvent $event)
    {
        if ($event->getRequest()->getMethod() === 'OPTIONS') {
            $headers = CorsSettings::getHeadres();
            $response = new JsonResponse([
                'method' => $event->getRequest()->getMethod(),
                ], 200, $headers);
            $event->setResponse($response);
        }

    }
}

где:

  • CorsSettings::getHeadres() -- вернет массив нужных заголовков (у вас может быть другая реализация)
  • $event->getRequest() -- получив запросы вы можете также узнать и маршрут (если требуется разрешить что-то только для отдельных маршрутов)

далее в:

config/services.yaml

добавим:

app.options_listener:
	class: App\EventListener\RequestListener
	tags:
		- { name: kernel.event_listener, event: kernel.request, priority: 33 }

Чтобы узнать нужный приоритет, можно использовать команду:

bin/console debug:event-dispatcher kernel.request

-- в нашем случае нам нужно иметь приоритет ниже (чтобы выполняться позже) чем RouterListener::onKernelRequest() (на момент написания этих строк в симфони 7 у него 32 приоритет)

Без конфигурации в services.yaml

Аналогичный функционал можно получить и без правки config/services.yaml, если
в стандартную автозагружаемую директорию:

src/EventSubscriber

добавить класс:

// src/EventSubscriber/TokenSubscriber.php
namespace App\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Security\CorsSettings;


class TokenSubscriber implements EventSubscriberInterface
{
    public function onKernelRequest(RequestEvent $event)
    {
        $request = $event->getRequest();
  
        if ($request->getMethod() == 'OPTIONS') {
            $event->setResponse( // изменяем ответ
                new JsonResponse('', 200, CorsSettings::getHeadres())
            );
        }
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::REQUEST  => ['onKernelRequest', 2049],
        ];
    }
}

Спасибо за решение: @math2

Источники:

  1. https://symfony.com/doc/current/event_di...
  2. https://symfony.com/doc/current/referenc...

Вариант для установки CORS для OPTIONS (не из контроллера) и других ответов (из контроллера)

Такой вариант решает проблему корс в целом для приложения (не только для Options, но и для ответов контроллеров):


<?php

namespace App\EventListener;

use App\Security\CorsSettings;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;


final class CorsEventListener
{

    private const OPTIONS_NAME = 'OPTIONS';

    protected string $lastRequestMethod = '';

    public function onKernelRequest(RequestEvent $event)
    {
        $request = $event->getRequest();
        $this->lastRequestMethod = strtoupper($request->getMethod());
        if ($this->lastRequestMethod === self::OPTIONS_NAME) {
            $response = new Response('', 204);
            $response->headers->add(CorsSettings::getOptionsHeaders());
            $event->setResponse($response);
        }
    }

    /**
     * @see symfony.com/doc/current/event_dispatcher.html
     * @see symfony.com/doc/current/reference/events.html
     * @see stackoverflow.com/a/59715685
     */
    public function onKernelResponse(ResponseEvent $event): void
    {
        // Для OPTIONS все настройки уже установлены в onKernelRequest()
        if ($this->lastRequestMethod !== self::OPTIONS_NAME) {
            $response = $event->getResponse();
            $response->headers->add(CorsSettings::getHeaders());
        }
    }
}

-- тут для OPTIONS мы перехватываем request, а для остальных методов - response
и в config/services.yaml:

    App\EventListener\CorsEventListener:
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 }
            - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }

А также возможная реализация CorsSettings:

<?php

namespace App\Security;
use ItForFree\rusphp\Network\Url;

/**
 * Настройки для CORS
 */
class CorsSettings
{

    const ACCESS_CONTROL_ALLOW_METHODS = 'GET, PUT, POST, DELETE, OPTIONS';

    /**
     * Заголовки для поддержки кросс-доменных запросов
     *
     * @return array
     */
    public static function getOptionsHeaders()
    {
        $headers = [
            'Access-Control-Allow-Origin' => self::getAccessControlOriginValue(),
            'Access-Control-Allow-Credentials' => 'true',
            'Access-Control-Allow-Methods' => self::ACCESS_CONTROL_ALLOW_METHODS,
            'Access-Control-Max-Age' => 1,
            'Access-Control-Allow-Headers' => 'Content-Type, Content-Range, Content-Disposition, Content-Description, X-AUTH-TOKEN, Set-Cookie'
        ];
        return $headers;
    }

    /**
     * Заголовки для поддержки кросс-доменных запросов
     *
     * @return array
     */
    public static function getHeaders()
    {

        $headers = [
            'Access-Control-Allow-Origin' => self::getAccessControlOriginValue(),
            'Access-Control-Allow-Credentials' => 'true',
        ];
        return $headers;
    }
	
	protected static function getAccessControlOriginValue()
	{
		$allowedOrigins = ''; // по умолчанию запрещаем с других доменов
		$origin = !empty($_SERVER['HTTP_ORIGIN']) ?
			$_SERVER['HTTP_ORIGIN'] : '';
		
		if (($_SERVER['APP_ENV'] == 'dev')
			&& !empty($origin)) {
			$allowedOrigins = $origin;	
		}
		
		return $allowedOrigins;
	}
}

Материалы по теме

vedro-compota's picture

О выяснении приоретата обработчиков событий см. тут https://fkn.ktu10.com/?q=node/10983

_____________
матфак вгу и остальная классика =)

vedro-compota's picture

Можно перехватывать и событие запроса и событие ответа:

<?php

namespace App\EventListener;

use App\Security\CorsSettings;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;


final class CoreEventListener
{

    private const OPTIONS_NAME = 'OPTIONS';

    protected string $lastRequestMethod = '';

    public function onKernelRequest(RequestEvent $event)
    {
        $request = $event->getRequest();
        $this->lastRequestMethod = strtoupper($request->getMethod());
        if ($this->lastRequestMethod === self::OPTIONS_NAME) {
            $response = new Response('', 204);
            $response->headers->add(CorsSettings::getOptionsHeaders());
            $event->setResponse($response);
        }
    }

    /**
     * @see https://symfony.com/doc/current/event_di...
     * @see https://symfony.com/doc/current/referenc...
     * @see https://stackoverflow.com/a/59715685
     */
    public function onKernelResponse(ResponseEvent $event): void
    {
        if ($this->lastRequestMethod === self::OPTIONS_NAME) {
            $response = $event->getResponse();
            $response->headers->add(CorsSettings::getHeaders());
            // ... modify the response object
        }
    }
}

и в config/services.yaml:

    # Register EventListener onKernelResponse
    App\EventListener\CoreEventListener:
        tags:
            - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 2049 }
            # - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }

-- работает в symfony 7

_____________
матфак вгу и остальная классика =)