Rauth — это пакет контроля доступа SitePoint для предоставления или ограничения доступа к определенным классам или методам, в основном посредством аннотаций.
В этом уроке мы узнаем, как его использовать.
Почему Раут
Традиционные уровни контроля доступа (ACL) контролируют только маршруты — вы устанавливаете все, что начинается с /admin
чтобы оно было доступно только администраторам, и так далее. Это хорошо для большинства случаев, но не когда:
- Вы хотите контролировать доступ в командной строке (нет маршрутов там)
- вы хотите, чтобы ваш уровень доступа не изменился, даже если вы измените маршруты
Раут была разработана для удовлетворения этой потребности. Естественно, он также будет работать очень хорошо вместе с любым другим типом ACL, если его функции недостаточны.
Аннотации плохие ™
Несколько «спорно», Rauth по умолчанию с помощью аннотаций для управления доступом. Независимо от того, в каком лагере вы находитесь в отношении аннотаций в PHP, вот почему их использование в случае с Раутом далеко не так неправильно, как об этом думают некоторые :
-
так как вы обычно управляете доступом к контроллерам и действиям в типичном приложении MVC, жестко связывайте их с Rauth, как будто это не только безвредно (контроллеры почти всегда должны быть полностью отброшены и переписаны, если вы меняете фреймворки или структуру приложения в основном), он также дает вам мгновенное понимание того, какой класс / метод имеет какие требования ACL
-
если вам не нравятся аннотации, вы можете предоставить Rauth предварительно кэшированный или предварительно проанализированный список разрешений и классов, к которым они применяются, поэтому можно полностью избежать проблемы с аннотациями.
-
больше нет страха, что аннотации замедляют работу, потому что PHP должен отражаться в рассматриваемых классах и извлекать их каждый раз. При постоянном включении OpCache это происходит только один раз, а с собственной поддержкой кэша Rauth его можно сохранить даже в другом месте, а проход чтения аннотации можно вообще избежать.
Простой пример
Для простого примера давайте создадим два класса, файл index.php
и установим Rauth:
composer require sitepoint / rauth
<?php // index.php use SitePoint \ Rauth ; require_once 'vendor/autoload.php' ; $r = new Rauth ( ) ; $user = [ 'groups' = > 'admin' , ] ; $fakeRoutes = [ 'admin' = > [ 'One' , 'adminOnly' ] , 'users' = > [ 'One' , 'users' ] , 'banned' = > [ 'One' , 'banned' ] , 'everyone' = > [ 'Two' , 'everyone' ] , ] ; foreach ( $fakeRoutes as $route ) { require_once $route [ 0 ] . '.php' ; $class = $route [ 0 ] ; $method = $route [ 1 ] ; try { $r - > authorize ( $class , $route [ 1 ] , $user ) ; echo "Success: " ; $class = new $class ( ) ; $class - > $method ( ) ; } catch ( Rauth \ Exception \ AuthException $e ) { echo "Authorizing {$class}::{$route[1]} failed!\n" ; echo $e - > getMessage ( ) . "\n" ; } }
<?php // One.php /** * Class One * @auth-groups users */ class One { /** * @auth-groups admin */ public function adminOnly ( ) { echo 'Because the "admin" group is detected, the One::adminOnly method is executed.' ; echo "\n" ; } public function users ( ) { echo '"Users" can use One::users - it inherited from the class @auth declaration' ; echo "\n" ; } /** * @auth-groups banned * @auth-mode none */ public function banned ( ) { echo 'No user with the group "banned" can access One::banned.' ; echo "\n" ; } }
<?php // Two.php class Two { public function everyone ( ) { echo "Everyone can access Two::everyone!\n" ; } }
Если мы теперь запустим это в консоли с:
php index.php
мы получим:
vagrant@homestead : ~ / Code / rauthtest$ php index . php Success : Because the "admin" group is detected , the One : : adminOnly method is executed . Authorizing One : : users failed ! Success : No user with the group "banned" can access One : : banned . Success : Everyone can access Two : : everyone !
Если мы изменим группу массива $user
на banned
, посмотрим, что произойдет:
vagrant@homestead:~/Code/rauthtest$ php index.php Authorizing One::adminOnly failed! Authorizing One::users failed! Authorizing One::banned failed! Success: Everyone can access Two::everyone!
В двух словах, Rauth использует требования (все начинается с @auth-
in docblocks) и сравнивает их с атрибутами (все, что мы определяем в переменной $user
). Вам решать, как вы на самом деле получите эти атрибуты — возможно, вы будете использовать какой-то готовый пакет, такой как Gatekeeper, или вы будете использовать собственную систему аутентификации, не имеет значения. Важно то, что Раут получает атрибуты в формате, который сопоставим с требованиями к классам / методам.
Примечание по режимам: если у пользователя есть групповой атрибут «admin», он может получить доступ ко всем методам с помощью @auth-groups admin
. Если mode
установлен на @auth-mode OR
(это значение по умолчанию и его можно опустить), то пользователь сможет получить доступ к методам с помощью @auth-groups admin, users, banana
, потому что им нужен только один из группы определены. Когда режим AND ( @auth-mode AND
), они должны иметь ВСЕ группы. Если режим NONE
как в методе banned()
, пользователь НЕ ДОЛЖЕН иметь ЛЮБОЙ из групп.
Помните, что auth-
может быть чем угодно — не обязательно должна быть groups
. Вы можете использовать совершенно произвольные значения, такие как @auth-banana ripe
и тогда пользователь с атрибутом 'banana' => 'ripe'
сможет получить доступ к классу / методу.
Пример внедрения зависимости
Приведенный выше пример представляет собой элементарную демонстрацию потока авторизации. Теперь давайте настроим ситуацию, в которой маршрут разрешен, контроллер автоматически вызывается контейнером внедрения зависимостей, и проверка авторизации выполняется автоматически. Другими словами, основанный на передовом опыте подход к запуску нового приложения — один из них — фреймворк без фреймворка — основан на.
composer require sitepoint / rauth php - di / php - di nikic / fast - route
Давайте также Controllers
папку Controllers
и поместим туда два контроллера:
<?php // Controllers/OneController.php namespace MyApp ; class OneController { public function homeAction ( ) { echo "This is the home screen!" ; } }
<?php namespace MyApp ; /** * Class AnotherController * @package MyApp */ class AnotherController { public function indexAction ( ) { echo "This is the second controller, index action, accessible to all" ; } /** * @auth-groups users */ public function onlyLoggedInAction ( ) { echo "This action can only be accessed by logged in users" ; } /** * @auth-groups users * @auth-mode NONE */ public function onlyLoggedOutAction ( ) { echo "This action can only be accessed by visitors who are not logged in" ; } }
Нам также потребуется настроить автозагрузчик для загрузки этих контроллеров:
"autoload" : { "psr-4" : { "MyApp\\" : "Controllers" } }
И восстановите файлы автозагрузки с:
composer du
Следующим шагом является настройка маршрутов и контейнера внедрения зависимостей в соответствии с документацией PHP-DI. Наш файл index.php
должен быть изменен так:
<?php // index.php require_once 'vendor/autoload.php' ; use SitePoint \ Rauth ; use FastRoute \ RouteCollector ; use FastRoute \ Dispatcher ; use DI \ ContainerBuilder ; $containerBuilder = new ContainerBuilder ; $containerBuilder - > addDefinitions ( [ ] ) ; $container = $containerBuilder - > build ( ) ; $routeList = [ [ 'GET' , '/one-home' , [ 'MyApp\OneController' , 'homeAction' ] ] , [ 'GET' , '/another-index' , [ 'MyApp\AnotherController' , 'indexAction' ] ] , [ 'GET' , '/another/loggedin' , [ 'MyApp\AnotherController' , 'onlyLoggedInAction' ] ] , [ 'GET' , '/another/loggedout' , [ 'MyApp\AnotherController' , 'onlyLoggedOutAction' ] ] , ] ; /** @var Dispatcher $dispatcher */ $dispatcher = FastRoute\ simpleDispatcher ( function ( RouteCollector $r ) use ( $routeList ) { foreach ( $routeList as $routeDef ) { $r - > addRoute ( $routeDef [ 0 ] , $routeDef [ 1 ] , $routeDef [ 2 ] ) ; } } ) ; $route = $dispatcher - > dispatch ( $_SERVER [ 'REQUEST_METHOD' ] , $_SERVER [ 'REQUEST_URI' ] ) ; switch ( $route [ 0 ] ) { case FastRoute\ Dispatcher : : NOT_FOUND : die ( '404 not found!' ) ; case FastRoute\ Dispatcher : : METHOD_NOT_ALLOWED : die ( '405 method not allowed' ) ; case FastRoute\ Dispatcher : : FOUND : $controller = $route [ 1 ] ; $parameters = $route [ 2 ] ; $container - > call ( $controller , $parameters ) ; break ; }
Сначала мы делаем новый пустой контейнер. Затем мы определяем некоторые маршруты (обычно это делается во внешнем файле, например routes.php
). Мы добавляем эти маршруты в диспетчер, чтобы он знал, что с ними делать, и, наконец, мы извлекаем элементы запускаемого маршрута и запускаем соответствующий контроллер. Достаточно просто, верно?
Теперь маршруты должны работать:
Пора добавить слой Rauth, чтобы фактически ограничить доступ к некоторым из этих маршрутов.
Сначала мы добавим определение User
в контейнер:
$containerBuilder - > addDefinitions ( [ 'User' = > function ( ) { return [ 'groups' = > 'users' , ] ; } , ] ) ;
Обычно в этом есть какая-то логика, например, проверка базы данных для групп пользователей. Для целей данного руководства достаточно жесткого кодирования, поскольку фактическое управление пользователями выходит за рамки этой части.
Затем, в приведенном ниже случае FOUND
, мы создадим экземпляр класса Rauth и создадим массив атрибутов для его подачи, когда пришло время выполнить проверку authorize
. Обратите внимание, что по умолчанию мы используем пустой массив groups
если пользователь не был определен (т.е. он вышел из системы).
// [...] case FastRoute\ Dispatcher : : FOUND : $controller = $route [ 1 ] ; $parameters = $route [ 2 ] ; $rauth = new Rauth ( ) ; $attributes = $container - > get ( 'User' ) ? : [ 'groups' = > [ ] ] ;
Поскольку Rauth создает AuthException
при сбое вызова authorize
, давайте также AuthException
все это в блок try/catch
.
try { $rauth - > authorize ( $controller [ 0 ] , $controller [ 1 ] , $attributes ) ; } catch ( Rauth \ Exception \ AuthException $e ) { die ( "Authorization failed" ) ; }
Если мы сейчас получим доступ к another/loggedout
маршруту another/loggedout
с ранее another/loggedout
пользователем по умолчанию, он должен потерпеть неудачу, потому что у нас есть группа users
для этого пользователя:
Пока another/loggedin
группа another/loggedin
будет успешной:
Конечно, в процессе разработки вам могут потребоваться более подробные сообщения об ошибках. Для этого каждое AuthException
имеет type
и содержит несколько объектов Reason
. Типом будет что-то вроде «none», если режим был установлен на «NONE», на «ban», если сбой произошел из-за бана (есть разница — см. Следующий раздел) и т. Д. Каждая из причин будет иметь свои собственные группы сбоев (не путать с произвольно названным атрибутом «группа») и список всех объектов в этой группе, которые он имеет против ожидаемых. Возможно, это лучше всего объяснить на примере. Мы можем заменить вызов die()
выше на:
echo 'Failed due to: ' . $e - > getType ( ) . '. ' ; /** @var Rauth\Exception\Reason $reason */ foreach ( $e - > getReasons ( ) as $reason ) { echo 'Blocked by "' . $reason - > group . '" when comparing owned "' . implode ( ", " , $reason - > has ) . '" versus "' . implode ( ", " , $reason - > needs ) . '".' ; } die ( ) ;
Вы бы хотели, чтобы это было в виде флеш-сообщения или в виде обычного сообщения об ошибке в обычном приложении, но в этом уроке это будет отражено, как будто это хорошо. Этот блок приводит к выводу, подобному этому, если мы снова попробуем loggedout
:
Другими словами, он говорит: «Я не ожидаю ничего из« групп »:« пользователи », но я получил« пользователей »».
Не стесняйтесь изменять некоторые жестко закодированные группы в пользовательском массиве и изменять аннотации для классов для дальнейшего тестирования некоторых маршрутов. Проверьте режим AND
против OR
тоже.
Запреты
Последнее, что мы не упомянули до сих пор: запреты. У Раут есть дополнительная предварительная проверка для запрещенных сущностей, которая предшествует всем остальным. Если выполняется запрет, то авторизация не пройдена, независимо от того, сколько других совпадений совпадает. Например:
@auth - groups users , admin @auth - ban - groups banned
Вышеуказанное заблокирует доступ к классу или методу, если у пользователя есть banned
группа, независимо от того, есть ли у него также users
или admin
. Как и другие атрибуты, коллекции банов могут быть произвольными:
@auth - groups users , admin @auth - ban - tags banana
Пометьте пользователя banana
и он не сможет войти, независимо от остальных атрибутов.
Проверки банов выполняются ДО любой другой проверки, поэтому, если вам просто нужно заблокировать доступ к некоторым классам или методам, основанным на некоторых атрибутах, запрет будет наиболее эффективным подходом. Баны имеют свой собственный тип в исключении:
Вывод
Мы взглянули на Rauth , пакет SitePoint для контроля доступа к классам и методам. Мы реализовали это в двух простых демонстрациях и показали, насколько это удобно.
Поскольку Rauth является разработкой SitePoint с открытым исходным кодом , мы активно ищем авторов или просто отзывы по некоторым открытым вопросам — мы полны решимости сделать наши пакеты достойными для производственного использования, и нам нужна ваша помощь для этого.
Вы уже дали шанс Раут? Не могли бы вы? Дайте нам знать об этом в комментариях!