Аутентификация и авторизация пользователей в Catalyst-приложении. Аутентификация на основе
DBI. Работа с Catalyst::Authentication::Store::DBI::ButMaintained и Catalyst::Authentication::Credential::Password. Создание таблицы users в БД.
Создание системы сессий, авторизации и аутентификации в Catalyst. Часть 1. Сессии в Catalyst
Создание системы сессий, авторизации и аутентификации в Catalyst. Часть 3. Роли пользователей
Ранее, в Catalyst-приложение была добавлена работа с сессиями. Однако, приведенные примеры кода еще не полностью работоспособны. Например, плагин Session::PerUser позволяет использовать сессии не авторизовавшимся клиентам. Но при этом, для работы ему необходим плагин Authentication. Session::PerUser использует его функции в своей работе. Таким образом, логическим продолжением работы с сессиями, является добавление в Catalyst-приложение системы аутентификации.
Наиболее часто встречающийся прием, это реализация аутентификации через DBIx и работу со схемами. Есть множество примеров реализации, для разных баз данных. Я решила не отступать от принятого решения и использовать DBI (Catalyst::Model::DBI работает через DBIx::Connector). Это создало свои трудности. Оказалось, что аутентификация через DBI крайне редкий вариант, на который практически нет полных работающих примеров. Только небольшие отрывки кодов.
Так что, приведенный пример реализации - в некотором роде, уникальный.
Что было сделано в процессе работы:
- создана страница входа в систему, выхода из системы,
- регистрации нового пользователя,
- добавлена страница профиля пользователя,
- логины и пароли пользователей хранятся в БД mysql,
- пароли шифруются MD5.
Все примеры приводятся с минимально возможным объемом кода, чтобы не отвлекаться от главного. Красивые стили, правильное форматирование html-страниц, обработка ошибок и исключений в коде - это можно сделать позднее. А пока - чистая реализация системы аутентификации.
Введение
Логика работы с пользователями делится на 2 шага:
- Аутентификация пользователя. На этом шаге клиент должен подтвердить, что является тем, за кого себя выдает. Обычно для этого достаточно ввода логина и пароля. Возможна расширенная аутентификация, когда пользователь должен ответить на дополнительные вопросы. В сложных системах, связанных с финансами, могут быть дополнительные проверки, типа подтверждение по смс, использование электронных сертификатов для подтверждения своих прав, и т.п.
- Авторизация. Проверка прав конкретного пользователя на доступ к тем или иным данным. И предоставление этого доступа.
Для аутентификации пользователей используется плагин Catalyst - Plugin::Authentication . Это основа для выстраивания системы аутентификации. Сам по себе плагин не является самодостаточным.
Настройки Plugin::Authentication делятся на две части.
- Блок Store - используется для организации хранения информации о пользователе. Как и где будет храниться информация, определяет именно блок Store.
- Блок Credentials - это механизм идентификации пользователей.
Пара Store и Credentials объединяются в "тип авторизации" - "realm". Таких типов для одного приложения может быть определено несколько штук. Например, в Catalyst-приложении используется авторизация с помощью соц.сетей. И каждый пользователь может выбрать, какую именно соц.сеть (Твиттер, Одноклассники, LiveJournal и т.п.) он хочет использовать для авторизации.
Плагин Plugin::Authentication сам не отвечает за работу с данными. И для Store, и для Credentials указывается специальный модуль, который будет реализовывать необходимую функциональность.
Store
В качестве хранилища данных в моем случае используется БД mysql, а для работы с ней - DBI.
Чтобы заставить авторизацию работать через DBI, без схем, DBIx и прочего - пришлось постараться. Был найден специальный модуль Catalyst::Authentication::Store::DBI::ButMaintained.
Catalyst::Authentication::Store::DBI::ButMaintained является форком от Catalyst::Authentication::Store::DBI. Catalyst::Authentication::Store::DBI в настоящее время не поддерживается автором в работоспособном и актуальном состоянии, и попытка использовать его окончилась неудачей.
Главная задача модуля, который обслуживает раздел Store - обеспечить загрузку информации о пользователе, для ее дальнейшего использования.
Credentials
Для верификации используется модуль Catalyst::Authentication::Credential::Password, который входит в состав Plugin::Authentication и не требует отдельной установки.
Catalyst::Authentication::Credential::Password в момент вызова метода $c->authenticate получает данные о логине и пароле пользователя в качестве аргументов, и пытается сравнить их с данными из хранилища. Кроме того, пароль в БД хранится в зашифрованном состоянии. Catalyst::Authentication::Credential::Password способен работать с несколькими алгоритмами шифрования, поэтому от программиста не требуется шифровать пароли, прежде чем передать их методу $c->authenticate . Просто необходимо заранее указать в настройках используемый способ шифрования.
Кроме Catalyst::Authentication::Credential::Password часто используются модули Catalyst::Authentication::Credential::HTTP, Catalyst::Authentication::Credential::OpenID, Catalyst::Authentication::Credential::CAS и др. Кроме того, никто не мешает вам самостоятельно написать подобный модуль. Если вы активно работаете с соц.сетями, вам, возможно, придется это сделать. Т.к. найти готовый модуль на cpan для авторизации под одной из местных соц.сетей - затруднительно.
Примечание. Слово "credentials" - сложно адекватно перевести на русский язык, особенно в IT-контексте. Ближе всего его значение, в текущем случае, к словосочетанию "учетные данные". В параметрах, которые прописываются для "credentials", указывается, какой "тип авторизации" будет использоваться. Т.е. Catalyst::Authentication::Credential::OpenID - использует для аутентификации пользователя технологию OpenID, а Catalyst::Authentication::Credential::CAS - технологию Central Authentication Service (CAS).
Самое главное, что придется сделать при написании собственного Credential-модуля - реализовать свой метод authenticate().
Реализация системы аутентификации в Catalyst
1. Создаем таблицу в БД для хранения логинов и паролей пользователей.
|
1 2 |
CREATE TABLE users (id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, name CHAR(30), password TEXT, email TEXT, is_active BOOLEAN NOT NULL DEFAULT 1); |
2. Устанавливаем модуль
|
1 2 |
cpan force install Catalyst::Authentication::Store::DBI::ButMaintained |
3. Создаем новый контроллер, для демонстрации работы системы аутентификации.
|
1 |
perl script/app_create.pl controller Profile |
4. Добавляем в app.pm использование аутентификационного модуля и конфигурацию для него.
lib/app.pm :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
use Catalyst qw/ ... Authentication Session Session::PerUser Session::Store::DBI Session::State::Cookie /; ... __PACKAGE__->config( ... 'Plugin::Authentication' => { 'default_realm' => 'default', 'use_session' => 1, 'realms' => { 'default' => { 'credential' => { 'class' => 'Password', 'password_field' => 'password', 'password_type' => 'hashed', 'password_hash_type' => 'MD5', }, 'store' => { 'class' => 'DBI::ButMaintained', 'user_table' => 'users', 'user_key' => 'id', 'user_name' => 'name', 'role_table' => 'roles', 'role_key' => 'id', 'role_name' => 'name', 'user_role_table' => 'user_roles', 'user_role_user_key' => 'user_id', 'user_role_role_key' => 'role_id', }, }, }, }, ); |
5. На данном этапе, надо создать для пользователей формы ввода логина и пароля. Кроме того, для того, чтобы не создавать пользователей в БД вручную, да еще вручную шифруя пароли по форме MD5, метод для регистрации пользователя на сайте тоже реализуем.
lib/app/Controller/Root.pm :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
sub login :Path('/login') :Args(0) { my ( $self, $c ) = @_; my $username = $c->request->params->{username}; my $password = $c->request->params->{password}; if ($username && $password) { unless ($c->authenticate({ 'name' => $username, 'password' => $password}) ) { $c->stash->{error} = 'auth_error'; $c->stash->{template} = 'auth/login.tt'; $c->forward('View::TT'); } $c->response->redirect($c->uri_for('/profile')); } else { $c->stash->{template} = 'auth/login.tt'; $c->forward('View::TT'); } } sub logon :Path('/logon') :Args(0) { my ( $self, $c ) = @_; my $username = $c->request->params->{username}; my $password = $c->request->params->{password}; if ($username && $password) { my $digest = md5_base64($password); my $dbh = $c->model('DBI')->dbh; my $sql = qq{INSERT INTO users(name, password, is_active) VALUES ('$username', '$digest', 1)}; my $rows = $dbh->do($sql); $c->stash->{template} = 'auth/login.tt'; $c->forward('View::TT'); } else { $c->stash->{template} = 'auth/logon.tt'; $c->forward('View::TT'); } } sub logout :Path('/logout') :Args(0) { my ( $self, $c ) = @_; $c->logout(); $c->stash->{template} = 'auth/logout.tt'; $c->forward('View::TT'); } |
lib/app/Controller/Profile.pm :
|
1 2 3 4 5 6 7 8 |
sub index :Path :Args(0) { my ( $self, $c ) = @_; if ($c->user()) { $c->response->body('Matched app::Controller::Profile in Profile.'); } else { $c->response->body('Access failed'); } } |
Таким образом, у нас получится 4 страницы:
- страница для регистрации пользователя, по адресу http://localhost:3000/logon
- страница авторизации зарегистрированного пользователя, по адресу http://localhost:3000/login
- секретная страница профиля, которая пока не содержит информации, но доступ на которую возможен только после авторизации http://localhost:3000/profile
- страница выхода из системы, по адресу http://localhost:3000/logout
6. Создаем шаблоны для каждой из перечисленных страниц.
root/src/auth/login.tt :
|
1 2 3 4 5 6 7 8 9 |
<form action="/login" method="POST"> Логин <input type="text" size="10" name="username" maxlength="40"> <br> Пароль <input type="text" size="10" name="password" maxlength="20"> <br> <input value="Войти" type="submit"> </form> |
root/src/auth/logon.tt :
|
1 2 3 4 5 6 7 8 9 10 |
Создаем нового пользователя в БД <form action="/logon" method="POST"> Логин <input type="text" size="10" name="username" maxlength="40"> <br> Пароль <input type="text" size="10" name="password" maxlength="20"> <br> <input value="Регистрация" type="submit"> </form> |
root/src/auth/logout.tt :
|
1 |
Ваша сессия завершена! |
7. Теперь можно запустить сервер и походить по страницам. Сначала создать пользователя, потом попробовать авторизоваться.
Пример содержимого таблицы users в БД, после того, как было создано 5 пользователей.
После авторизации в системе, в cookies записываются данные:

После авторизации, содержимое таблицы сессий в БД, стало выглядеть немного иначе:
Полезные ссылки по теме "Аутентификация в Catalyst-приложении"
catalystframework.org: Catalyst Advent - Day 14 - Authentication/Authorization
search.cpan.org: Catalyst::Authentication::Store::DBI::ButMaintained
search.cpan.org: Catalyst::Plugin::Session::PerUser
search.cpan.org: Catalyst::Plugin::Authentication
search.cpan.org: Catalyst::Authentication::Credential::Password
search.cpan.org: Catalyst::Manual::Tutorial::05_Authentication
Поставьте пожалуйста в верстке бэкграунд боди - фиксед, а то читать невозможно, рябит...