В разделе Плюс+ я выкладываю курс по 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');
Которая выполняется после того, как мы проверили имя и пароль. Если имя и пароль соответствуют администратору, то мы сохраняем в сессии соответствующих флаг. Этот контроллер чисто набросок для того, чтобы показать возможный вариант использования подхода.
Я не могу сказать, что это самое лучшее решение, как я уже сказал, есть несколько вариантов решения. Но вот почему-то нравится мне именно этот подход.
Паника, что-то случилось!!! Ничего не найдено в комментариях. Срочно нужно что-то добавить, чтобы это место не оставалось пустым.
Добавить Комментарий