Александр Борисов, 17 августа 2014
Задачу авторизации в приложении на Ruby on Rails решают различные гемы: cancan, cancancan, protector. Я хочу поделиться решением для авторизации на основе очень простого гема six.
Как правило, у нас в приложении есть текущий пользователь – гость или известный нам пользователь – и мы хотим понять две вещи: какие блоки на странице можно показывать текущему пользователю, и какие действия в контроллере текущий пользователь может выполнять.
Чтобы проверить, можно ли показывать тот или иной блок на странице текущему пользователю, удобно использовать условный оператор с методом can?
:
Для того, чтобы определить, может ли текущий пользователь выполнить запрошенное действие в контроллере удобнее всего использовать явный метод-проверку authorize!
, который поднимет исключение в случае, если доступ запрещён. Это исключение можно отлавливать и перенаправлять пользователя на страницу авторизации с сообщением об ошибке доступа, например.
Чтобы проверить, есть ли у текущего пользователя доступ к действию над ресурсом, нужно выполнить три шага:
А вообще, крайне рекомендую почитать исходный код six, там всего около 200 строк с подробными комментариями. Помимо того, что вы разберётесь с тем, как работает этот гем, вы обязательно почерпнёте что-нибудь для себя по теме написания хорошего кода.
Теперь нам необходимо реализовать методы can?
, authorize!
, и понять, каким образом мы будем привязывать список доступных действий к объектам в нашем приложении.
can?
и authorize!
Логичнее всего реализовывать методы проверки доступа в контроллерах, так как в модели MVC, которую использует Ruby On Rails, именно контроллеры отвечают за те действия, которые нужно выполнить в ответ на запрос пользователя. При этом метод can?
мы разрешим использовать в вьюшках с помощью helper_method
.
Реализацию лучше всего поместить в concern, так как в нашем приложении может быть несколько точек авторизации, например админка и публичная часть сайта.
Метод store_location
сохраняет в сессии адрес последнего выполненного запроса для последующего использования в redirect_back_or_default
. Сам store_location
вызывается с помощью хука after_action
на любой успешно выполненный GET-запрос пользователя на получение HTML.
Теперь мы можем использовать наш concern в контроллере:
В строке 6 мы ловим исключение AccessDenied
и обрабатываем его с помощью метода access_denied_handler
, в котором показываем сообщение о неудачной попытке доступа и отправляем пользователя на последнюю удачно запрошенную страницу с помощью redirect_back_or_default
.
В строке 11 параметр subject
имеет значение по умолчанию для использования метода can?
в виде <%= render 'cart' if can? :buy %>
во вьюшках – в этом случае поиск действия :buy
будет происходить в списке, который вернёт метод allowed
для current_user
.
В методе current_user
в строке 21 в случае, если пользователь не вводил логина и пароля, в качестве текущего пользователя используется объект класса Guest
. Это позволяет указывать список доступных действий для неавторизованного пользователя также, как и для авторизованного.
Теперь, чтобы использовать методы can?
и authorize!
на сайте, достаточно наследовать соответствующие контроллеры от app/controllers/web/application_controller.rb
.
В контроллере админки реализация будет отличаться только использованием current_admin_user
вместо current_user
, Admin::Guest
вместо Guest
, и другой обработкой неудачного доступа к ресурсу:
В каждом объекте, для которого мы проверяем список доступных действий, нам нужно реализовать метод allowed
, который будет возвращать список этих самых действий для текущего пользователя.
Метод allowed
вынесен в отдельный concern из-за того, что определение набора возможных действий над пользователем не относится напрямую к обязанностям класса User
. Сам concern лучше всего положить lib/allowed/user_rules.rb
– это соглашение позволяет быстро находить списки действий, относящиеся к тем или иным объектам приложении.
В строке 8 мы с помощью метода kind_of?
мы проверяем класс субъекта на предмет того, является ли сам класс субъекта или один из его классов-предков классом User
. Если не является, то возращаем пустой набор возможных действий.
В строках с 10 по 18 мы добавляем действия в набор возможных действий в зависимости от объекта – текущего пользователя.
В итоге у нас получилось лаконичное решение для авторизации, которое не расширяет базовые классы моделей и контроллеров без нашего ведома, и позволяет хранить логику разрешения действий над объектами в отдельных сущностях.
Мысли о веб-разработке на Ruby on Rails: работа с кодом, приёмы, инструменты, организация процесса разработки.
Веб-разработка на Ruby on Rails, реализация сложных проектов
mailbox@cifronomika.ru
+7 (910) 535-99-11