Не секрет, что ACL (access control lists) могут быть достаточно сложны в использовании. Поскольку Symfony рекомендует избирателей (voters) в качестве альтернативы ACL, я недавно решил, что напишу свой собственный простой в использовании Symfony 5 бандл для управления списками контроля доступа (ACL) в моих приложениях.
programarivm/easy-acl-bundle изначально был написан для использования в JWT-аутентифицированном API для одностраничных приложений (single page applications — SPA), но он также может быть полезен в ряде других сценариев, когда не требуется Security компонент — что в большинстве случаев, по моему скромному мнению, особенно подходит для сеансов обработки многостраничных приложений (multi-page applications — MPA).
EasyAclBundle.полностью полагается на сущности и репозитории Doctrine, что означает, что разрешения просто хранятся в базе данных без привязки к архитектуре вашего приложения.
Тем не менее, вот как легко JWT-токены аутентифицируются и авторизуются в подписчике событий с помощью так называемых easy ACL-репозиториев.
// src/EventSubscriber/TokenSubscriber.php namespace App\EventSubscriber; use App\Controller\AccessTokenController; use Doctrine\ORM\EntityManagerInterface; use Firebase\JWT\JWT; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\KernelEvents; class TokenSubscriber implements EventSubscriberInterface { public function __construct(EntityManagerInterface $em) { $this->em = $em; } public function onKernelController(ControllerEvent $event) { $controller = $event->getController(); // когда класс контроллера определяет несколько action методов, контроллер // возвращается как [$controllerInstance, 'methodName'] if (is_array($controller)) { $controller = $controller[0]; } if ($controller instanceof AccessTokenController) { $jwt = substr($event->getRequest()->headers->get('Authorization'), 7); try { $decoded = JWT::decode($jwt, getenv('JWT_SECRET'), ['HS256']); } catch (\Exception $e) { throw new AccessDeniedHttpException('Whoops! Access denied.'); } $user = $this->em->getRepository('App:User') ->findOneBy(['id' => $decoded->sub]); $identity = $this->em->getRepository('EasyAclBundle:Identity') ->findBy(['user' => $user]); $rolename = $identity[0]->getRole()->getName(); $routename = $event->getRequest()->get('_route'); $isAllowed = $this->em->getRepository('EasyAclBundle:Permission') ->isAllowed($rolename, $routename); if (!$isAllowed) { throw new AccessDeniedHttpException('Whoops! Access denied.'); } } } public static function getSubscribedEvents() { return [ KernelEvents::CONTROLLER => 'onKernelController', ]; } }
Большая часть этого кода не требует объяснения, если вы опытный разработчик; в основном, если входящий токен доступа успешно декодирован, что означает, что данный пользователь аутентифицирован, код пытается выяснить, имеет ли он права для доступа к текущему маршруту.
... $user = $this->em->getRepository('App:User') ->findOneBy(['id' => $decoded->sub]); $identity = $this->em->getRepository('EasyAclBundle:Identity') ->findBy(['user' => $user]); $rolename = $identity[0]->getRole()->getName(); $routename = $event->getRequest()->get('_route'); $isAllowed = $this->em->getRepository('EasyAclBundle:Permission') ->isAllowed($rolename, $routename); ...
Достаточно только двух easy ACL-репозиториев (Identity и Permission) для определения того, может ли пользователь получить доступ к текущему маршруту.
Конфигурация
Теперь давайте посмотрим в чем же вся магия. В целом, все заключается в определении маршрутов вашего приложения:
# config/routes.yaml api_post_create: path: /api/posts controller: App\Controller\Post\CreateController::index methods: POST api_post_delete: path: /api/posts/{id} controller: App\Controller\Post\DeleteController::index methods: DELETE api_post_edit: path: /api/posts/{id} controller: App\Controller\Post\EditController::index methods: PUT
А также разрешений:
# config/packages/programarivm_easy_acl.yaml programarivm_easy_acl: target: App\Entity\User permission: - role: Superadmin routes: - api_post_create - api_post_delete - api_post_edit - role: Admin routes: - api_post_create - api_post_edit - role: Basic routes: - api_post_create
Итак, теперь, если ваша схема базы данных обновлена:
php bin/console doctrine:schema:update --force
Четыре пустые таблицы будут добавлены в вашу базу данных:
easy_acl_identityeasy_acl_permissioneasy_acl_roleeasy_acl_route
Эта четверка идет рука об руку со следующими сущностями:
Programarivm\EasyAclBundle\Entity\IdentityProgramarivm\EasyAclBundle\Entity\PermissionProgramarivm\EasyAclBundle\Entity\RoleProgramarivm\EasyAclBundle\Entity\Route
И репозиториями:
Programarivm\EasyAclBundle\Repository\IdentityRepositoryProgramarivm\EasyAclBundle\Repository\PermissionRepositoryProgramarivm\EasyAclBundle\Repository\RoleRepositoryProgramarivm\EasyAclBundle\Repository\RouteRepository
Наконец, консольная команда easy-acl:setup предназначена для заполнения таблиц easy ACL.
php bin/console easy-acl:setup This will reset the ACL. Are you sure to continue? (y) y
Консоль MySQL:
mysql> select * from easy_acl_identity; Empty set (0.01 sec) mysql> select * from easy_acl_permission; +----+------------+-----------------+ | id | rolename | routename | +----+------------+-----------------+ | 1 | Superadmin | api_post_create | | 2 | Superadmin | api_post_delete | | 3 | Superadmin | api_post_edit | | 4 | Admin | api_post_create | | 5 | Admin | api_post_edit | | 6 | Basic | api_post_create | +----+------------+-----------------+ 6 rows in set (0.00 sec) mysql> select * from easy_acl_role; +----+------------+ | id | name | +----+------------+ | 1 | Superadmin | | 2 | Admin | | 3 | Basic | +----+------------+ 3 rows in set (0.00 sec) mysql> select * from easy_acl_route; +----+-----------------+---------+-----------------+ | id | name | methods | path | +----+-----------------+---------+-----------------+ | 1 | api_post_create | POST | /api/posts | | 2 | api_post_delete | DELETE | /api/posts/{id} | | 3 | api_post_edit | PUT | /api/posts/{id} | +----+-----------------+---------+-----------------+ 3 rows in set (0.00 sec)
Добавление идентификаторов пользователей
Концепция идентификаторов пользователей позволяет пакету вообще не вмешиваться в вашу базу данных, которая не изменяется им.
Как вы можете видеть, три EasyAcl таблицы заполнены данными, но, конечно же, это ваша задача — динамически определять идентификационные данные своих пользователей, как в примере, показанном ниже.
// src/DataFixtures/EasyAcl/IdentityFixtures.php namespace App\DataFixtures\EasyAcl; use App\DataFixtures\UserFixtures; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\FixtureGroupInterface; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Common\Persistence\ObjectManager; use Programarivm\EasyAclBundle\EasyAcl; use Programarivm\EasyAclBundle\Entity\Identity; class IdentityFixtures extends Fixture implements FixtureGroupInterface, DependentFixtureInterface { private $easyAcl; public function __construct(EasyAcl $easyAcl) { $this->easyAcl = $easyAcl; } public function load(ObjectManager $manager) { for ($i = 0; $i < UserFixtures::N; $i++) { $index = rand(0, count($this->easyAcl->getPermission())-1); $user = $this->getReference("user-$i"); $role = $this->getReference("role-$index"); $manager->persist( (new Identity()) ->setUser($user) ->setRole($role) ); } $manager->flush(); } public static function getGroups(): array { return [ 'easy-acl', ]; } public function getDependencies(): array { return [ RoleFixtures::class, UserFixtures::class, ]; } }
Для получения более подробной информации читайте документацию, которая проведет вас через процесс установки и настройки бандла easy ACL.
На этом все. Был ли этот пост полезен? Я надеюсь, что да. Расскажите нам в комментариях ниже!
Возможно, вас также заинтересует…
- Запись CASL React Abilities в JSON-файл с помощью команды Laravel Artisan.
- Сеанс SPA GUI как Non-HttpOnly Cookie
- Совет для ленивых разработчиков Symfony.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/508424/
Добавить комментарий