Як налаштувати користувацькі відповіді відмови у доступі

Дата оновлення перекладу 2023-09-26

Як налаштувати користувацькі відповіді відмови у доступі

У Symfony, ви можете викликати AccessDeniedException, щоб заборонити доступ користувачу. Symfony обробить це виключення і згенерує відповідь, засновану на стані атунетифікації:

  • Якщо користувач не аутентифікований (або аутентифікований анонімно), точка входу аутентифікації використовується для генерування відповіді (зазвичай, перенаправлення на сторінку входу у систему або відповіді 401 Неавторизовано);
  • Якщо користувач аутентифікований, але не має необхідних дозволів, генерується відповідь 403 Заборонено.

Налаштуйте відповідь відмови в авторизації

Вам потрібно створити клас, що реалізує AuthenticationEntryPointInterface. Цей інтерфейс має один метод (start()), який викликається, коли неаутентифікований користувач намагається отримати доступ до захищеного джерела:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Security/AuthenticationEntryPoint.php
namespace App\Security;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;

class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
{
    public function __construct(
        private UrlGeneratorInterface $urlGenerator,
    ) {
    }

    public function start(Request $request, AuthenticationException $authException = null): RedirectResponse
    {
        // додайте користувацьке флеш-повідомлення та перенаправте на сторінку входу у систему
        $request->getSession()->getFlashBag()->add('note', 'You have to login in order to access this page.');

        return new RedirectResponse($this->urlGenerator->generate('security_login'));
    }
}

Це все, якщо ви використовуєте конфігурацію services.yaml за замовчуванням . В іншому випадку, вам потрібно зареєструвати цей сервіс у контейнері.

Тепер, сконфігуруйте цей ID сервісу в якості точки входу у брандмауері:

1
2
3
4
5
6
7
# config/packages/security.yaml
firewalls:
    # ...

    main:
        # ...
        entry_point: App\Security\AuthenticationEntryPoint

Налаштуйте відповідь "заборонено"

Створіть клас, що реалізує AccessDeniedHandlerInterface. Цей інтерфейс визначає один метод під назвою handle(), де ви можете реалізувати будь-яку необхідну логіку, коли поточному користувачу відмовлено у доступі (наприклад, надіслати листа, зробити запис логу повідомлення або просто повернути користувацьку відповідь):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Security/AccessDeniedHandler.php
namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;

class AccessDeniedHandler implements AccessDeniedHandlerInterface
{
    public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response
    {
        // ...

        return new Response($content, 403);
    }
}

Якщо ви використовуєте конфігурацію services.yaml за замовчуванням , ви закінчили! Symfony автоматично дізнається про ваш сервіс. Потім, ви зможете сконфігурувати його під своїм брандмауером:

1
2
3
4
5
6
7
# config/packages/security.yaml
firewalls:
    # ...

    main:
        # ...
        access_denied_handler: App\Security\AccessDeniedHandler

Налаштування всіх відповідей відмови у доступі

У деяких випадках вам може захотітися і налаштувати відповіді, і виконати якусь дію (наприклад, вхід у систему) для кожного AccessDeniedException. У такому випадку, сконфігуруйте слухач kernel.exception :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/EventListener/AccessDeniedListener.php
namespace App\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class AccessDeniedListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            // пріоритет має бути більше, ніж у HTTP Security
            // ExceptionListener, щоб він викликався до слухача
            // виключень за замовчуванням
            KernelEvents::EXCEPTION => ['onKernelException', 2],
        ];
    }

    public function onKernelException(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();
        if (!$exception instanceof AccessDeniedException) {
            return;
        }

        // ... виконати якусь дію (наприклад, запис логів)

        // за бажанням, встановити користувацьку відповідь
        $event->setResponse(new Response(null, 403));

        // або припинити розповсюдження (запобігає виклику наступних слухачів виключень)
        //$event->stopPropagation();
    }
}