Русский
Русский
English
Статистика
Реклама

Перевод Простое управление ACL в Symfony

Перевод статьи подготовлен в преддверии старта курса Symfony Framework.



Не секрет, что 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.phpnamespace 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.yamlapi_post_create:    path:       /api/posts    controller: App\Controller\Post\CreateController::index    methods:    POSTapi_post_delete:    path:       /api/posts/{id}    controller: App\Controller\Post\DeleteController::index    methods:    DELETEapi_post_edit:    path:       /api/posts/{id}    controller: App\Controller\Post\EditController::index    methods:    PUT


А также разрешений:

# config/packages/programarivm_easy_acl.yamlprogramarivm_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_identity
  • easy_acl_permission
  • easy_acl_role
  • easy_acl_route


Эта четверка идет рука об руку со следующими сущностями:

  • Programarivm\EasyAclBundle\Entity\Identity
  • Programarivm\EasyAclBundle\Entity\Permission
  • Programarivm\EasyAclBundle\Entity\Role
  • Programarivm\EasyAclBundle\Entity\Route


И репозиториями:

  • Programarivm\EasyAclBundle\Repository\IdentityRepository
  • Programarivm\EasyAclBundle\Repository\PermissionRepository
  • Programarivm\EasyAclBundle\Repository\RoleRepository
  • Programarivm\EasyAclBundle\Repository\RouteRepository


Наконец, консольная команда easy-acl:setup предназначена для заполнения таблиц easy ACL.

php bin/console easy-acl:setupThis 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.phpnamespace 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.

На этом все. Был ли этот пост полезен? Я надеюсь, что да. Расскажите нам в комментариях ниже!

Возможно, вас также заинтересует...






Узнать о курсе подробнее.


Источник: habr.com
К списку статей
Опубликовано: 26.06.2020 16:08:22
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании otus. онлайн-образование

Php

Symfony

Acl

Категории

Последние комментарии

© 2006-2020, personeltest.ru