В разделе Плюс+ я выкладываю курс по Symfony и PHP и там я рассказываю от и до, а здесь поднят вопрос – как сделать админку так, чтобы обращения к защищенной области сайта происходил редирект на страницу входа. Это можно использовать для администраторских панелей или для других задач, когда нужно иметь защищенные области сайта.
Есть несколько способов решения этой задачи, а я расскажу тот, который мне нравится больше всего. Я не являюсь оригинальным автором идеи, не помню где я ее подсмотрел, но где-то увидел ее, попробовал и мне понравилось и уже долгие годы использую.
В папке, где вы храните модели создаем файл AdminAuthenticatedInterface.php. У меня модели хранятся в src/Model, более подробно о бизнес моделях я писал здесь: Бизнес логика в Symfony проектах.
В самом файле нам нужен просто интерфейс, у которого совершенно ничего не будет, потому что нам главное имя:
<?php namespace App\Model; interface AdminAuthenticatedInterface { }
Теперь мы будем делать защиту на основе этого интерфейса – если контроллер реализует его, то методы контроллера являются защищенными и должны быть доступны только авторизованным пользователям – администраторам. Если контроллер не реализует, то доступ открыт.
Продолжим считать, что мы создаем раздел администратора и для него вы создаете контроллер AdminController:
class AdminController extends Controller implements AdminAuthenticatedInterface
Никаких методов у интерфейса нет, поэтому реализовывать ничего не нужно, достаточно только сказать, что мы реализуем и все.
Теперь по идее к этому контроллеру доступ может получить только администратор, но пока только по идее, потому что еще нет такого кода, который бы реально реализовывал задуманную логику. Опять же в модели создаем следующий файл TokenListener.php:
<?php // src/EventListener/TokenListener.php namespace App\EventListener; use App\Model\AdminAuthenticatedInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Event\FilterControllerEvent; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Session\Session; class TokenListener { public function __construct() { } public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController(); if (!is_array($controller)) { return; } if ($controller[0] instanceof AdminAuthenticatedInterface && (new Session)->get('superadmin', '') == '') { $event->getRequest()->attributes->set('auth_failed', 'fail'); } } public function onKernelResponse(FilterResponseEvent $event) { if ($event->getRequest()->attributes->get('auth_failed') == 'fail') { $response = new RedirectResponse('/admin/index'); $event->setResponse($response); } } }
Идея идеи заключается в том, что мы создали класс TokenListener, который будет выполняться при обработке запросов, а точнее его метод onKernelController будет отрабатываться на запросы. Самое интересное здесь это вот эти строки, которые проверяют – является ли текущий контроллер реализацией интерфейса AdminAuthenticatedInterface. Если да, то это защищенная область и нужно убедиться, что текущий пользователь может видеть этот контроллер:
if ($controller[0] instanceof AdminAuthenticatedInterface && (new Session)->get('superadmin', '') == '') { $event->getRequest()->attributes->set('admin_auth_failed', 'fail'); }
Обычно индикатор того, что перед нами администратор храниться где-то в сессии. Если в сессии нет указателя, что текущий пользователь супер админ, то мы должны перенаправить пользователя на страницу ввода имени и пароля. Но именно в этом месте этого сделать не получиться, редирект можно сделать в другом методе – onKernelResponse.
Простейший вариант решения этой проблемы - выставляем флаг в атрибутах запроса, что с запросом проблема и пользователь реально не имеет права доступа к текущему функционалу, именно это происходит в этой строке:
$event->getRequest()->attributes->set('auth_failed', 'fail');
В вот теперь в методе onKernelResponse проверяем – если атрибут auth_failed установлен, то необходимо перенаправить на страницу ввода пароля, а это уже можно сделать в методе, который выполняется при обработке ответа в фреймворке Symfony. На это событие я зарегистрирую чуть позже вот этот метод:
public function onKernelResponse(FilterResponseEvent $event) { if ($event->getRequest()->attributes->get('auth_failed') == 'fail') { $response = new RedirectResponse('/login/index'); $event->setResponse($response); } }
Здесь мы проверяем, установлен ли атрибут ошибки доступа auth_failed. Если он установлен, то доступ к текущему контроллеру был запрещен, и пользователь не авторизован (или не имеет соответствующих прав) и его нужно перенаправить на страницу /login/index.
То, что мы создали этот файл, еще не значит, что он начнет сразу работать. Этот сервис нужно зарегистрировать в файле config/service.yaml, и именно эта настройка потерялась, когда я писал о том, что забыл включить авторизацию https://www.flenov.info/blog/show/Zabyl-podklyuchity-avtorizaciyu. То есть весь код был, но без регистрации в service класс TokenListener реально не будет выполняться при выполнении каждого запроса.
Итак, открываем файл и где-то в конце раздела services: (у меня это в конце файла) нужно добавить следующие строки:
app.tokens.action_listener: class: App\Model\TokenListener tags: - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse }
Вот здесь как раз реально и происходит привязка событий kernel.controller методу onKernelController и события kernel.response к методу onKernelResponse.
<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Routing\Annotation\Route; use App\Model\AdminAuthenticatedInterface; class AdminloginController extends Controller { /** * @Route("/login/index", name="loginindex", methods={"GET"}) */ public function indexAction() { return $this->render(login/index.html.twig', array('email' => '', 'validation' => '')); } /** * @Route("/login/index", name="loginpost", methods={"POST"}) */ public function loginAction(Request $request) { if ($request->request->get('email') == "flenov@mail.ru" && $request->request->get('password') == "password") { $session = new Session(); $session->set('superadmin', '1'); return $this->redirect('/admin/index'); } return $this->render('login/index.html.twig', array('email' => $request->request->get('email'), 'validation' => 'Sorry, something is wrong')); } }
Самое главное тут – это строка:
$session->set('superadmin', '1');
Которая выполняется после того, как мы проверили имя и пароль. Если имя и пароль соответствуют администратору, то мы сохраняем в сессии соответствующих флаг. Этот контроллер чисто набросок для того, чтобы показать возможный вариант использования подхода.
Я не могу сказать, что это самое лучшее решение, как я уже сказал, есть несколько вариантов решения. Но вот почему-то нравится мне именно этот подход.
Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание
Паника, что-то случилось!!! Ничего не найдено в комментариях. Срочно нужно что-то добавить, чтобы это место не оставалось пустым.
Добавить Комментарий