From: Alex Povolotsky <tarkhil@over.ru.>
Newsgroups: email
Date: Mon, 16 Mar 2009 17:02:14 +0000 (UTC)
Subject: Аутентификация в Perl web-фреймворке Catalyst
Практически любое веб-приложение так или иначе нуждается в реализации
трех А - Аутентификации, Авторизации и Аграничения доступа.
(в оригинале - Authentication, Authorization, Access control)
В связи с большой востребованностью этого общественно-полезного дела,
авторы Catalyst перенесли его из плугинов в собственно систему и сделали
модульным. Одно только забыли - написать к этому внятную документацию.
Пытаюсь восполнить этот пробел по мере сил. Пока - в части первого А.
Процесс Аутентификации состоит в том, чтобы убедиться, что клиент,
представившийся, как root, действительно таковым является. В принципе,
после этого полезно сохранить информацию о визите в сессии, и, если есть
такая возможность, вспомнить о нем что-нибудь полезное - дату последнего
логина, имя-фамилию, список долгов...
$c->user_exists возвращает 1, если у нас в сесии помечено, что
пользователь успешно залогинился, $c->user возвращает указатель на
объект (скорее всего, типа C::A::User::Hash, наследник
Class::Accessor::Fast), из которого можно извлечь все, что мы знаем о
юзере.
Для хранения информации служат модули Catalyst::Authentication::Store::*,
представленные готовыми модулями C::A::S::Null, C::A::S::Minimal и C::A::S::DBIx::Class.
Важнейшая функция C::A::S::* - find_user($authinfo, $c), где $authinfo -
это хеш с данными, используемыми для поиска, а $c в представлениях не
нуждается.
Null вообще не утруждается хранением информации - ему передают хеш с
данными, он и возвращает их со словами "Вот, я все о нем знаю". Null
очень удобен для внешних механизмов аутентификации.
Minimal хранит в собственной конфигурации всю информацию.
DBIx::Class, как несложно догадаться, хранит данные в базе. В настройках
DBIx::Class указывается, в каком классе хранятся данные и в каком поле -
идентификатор пользователя.
Еще DBIx::Class умеет автоматически добавлять успешно авторизованных
(внешней авторизацией) пользователей в базу, но это я как-нибудь в
другой раз расскажу.
Для проверки валидности пользователя служат модули
Catalyst::Authentication::Credential::* (штатно реализован модуль
C::A::C::Password).
Главная деталь этого модуля - функция authenticate, параметрами которой
служат любимый $c, которого и так все знают, $realm, о котором речь
пойдет ниже, и $authinfo, представляющий собой хеш разных полезных
данных, типа когтей, по которым еще древние римляне определяли льва.
Собственно, эта функция ищет информацию о пользователе и сверяет ее с
$authinfo, возвращая либо undef, означающий, что перед нами -
проходимец, или правильно благословленный хеш с информацией о
пользователе. Разумеется, для этого используется get_user.
C::A::Password поддерживает несколько видов паролей, включая такую
экзотику, как none.
Например, если мы используем штатную аутентификацию апача, то в
стандартную схему мы вписываемся очень легко и красиво - Password с
паролем типа none, и C::A::S::Null. /login мы защищаем http-авторизацией
из апачевского конфига, а все остальное - смотрим через сессию.
Для объединения Credential и Store служит понятие Realm (C::A::Realm).
Собственно, задача Realm'а - это объединить семь углов под одной крышей
хранение и способ проверки пользователя под неким именем, а также
добавить в $c функцию authentcate($userinfo, $realm).
Да, еще в $c есть функция user_in_realm($realm), которая проверяет не
только факт залогиненности юзера, но и принадлежность его к области.
Таким образом, мы получаем достаточно гибкую аутентификацию и некое
примитивное разделение привилегий.
Конфигурация всего этого в project.yml описана кривее всего.
В принципе, в свете вышеизложенного все понятно.
$c->authenticate({login => $form->param('login'), password => $form->param('password')})
делает там, внутри, $user = $c->model('Main::Users')->find({login => $form->param('login')}),
потом проверяет md5_hex($form->param('password')) на совпадение с $user->password,
при успехе все данные из $user закручиваются в C::A::User::Hash и возвращаются
обратно, при неуспехе возвращается null.