Аутентификация с использованием Jelly и Jelly-Auth
Рубрика: Web frameworks
Метки: Kohana | обучение
Среда, 14 апреля 2010 г.
Просмотров: 822
Подписаться на комментарии по RSS
Метки: Kohana | обучение
Среда, 14 апреля 2010 г.
Просмотров: 822
Подписаться на комментарии по RSS
Briefly in English
A short tutorial on using Kohana 3 Jelly and Jelly-Auth modules. English version.
Сразу хочу предупредить, что код не тестировался на безопасность и ошибкоустойчивость (все это будет позже при наличии времени). Так что в продакшн не выкладывать!
Краткие требования
1. закрытая зона /admin
2. логин/логаут (пока без автологина)
3. создание/редактирование/удаление пользователей из админки (нет открытой регистрации)
Редактируем bootstrap
Kohana::modules(array(
'database' => MODPATH.'database', // Database access
'jelly' => MODPATH.'jelly',
'jelly-auth' => MODPATH.'jelly-auth', // this is jelly-auth plug-in
'auth' => MODPATH.'auth', // Basic authentication
));Модули ORM и Auth должны быть подключены, причем модуль Auth д.б. подключен после Jelly-Auth во избежание многих труднопонимаемых глюков.
Пропишем роут админки перед более общим дефолтным
Route::set('admin', 'admin(/<action>(/<param>(/<id>)))')
->defaults(array(
'controller' => 'admin',
'action' => 'index',
));Создадим контроллер админки, унаследовав его от Controller_Template. Пока нам понадобятся только два свойства
public $template; protected $auth;
и следующие методы
public function before() (инициализация общих для всех методов свойств контроллера) public function action_index() (тестовая страница) public function action_login() (страница логина) public function action_users() (управление аккаунтами) public function action_logout() (выход из админки с редиректом) protected function _auto_nav() (автогенерация навигационного меню)
Создадим базу данных, добавим пользователя БД и запишем это все в конфиг application/config/database.php. Заодно скопируем modules/jelly-auth/config/auth.php в application/.
Создадим таблицы для модуля Jelly-Auth, воспользовавшись включенным в дистрибутив скриптом, и сразу же добавим в базу пользователя-администратора, поскольку открытой регистрации не будет.
INSERT INTO `users` (`id`, `email`, `username`, `password`, `logins`, `last_login`) VALUES<BR>(1, '', 'admin', 'a3b34d0f297748b8b5741113d9f1ff925d5d7651331a10e0b8', 0, NULL);
INSERT INTO `ko3bricks`.`roles_users` (`user_id`, `role_id`) VALUES ('1', '1'), ('1', '2');Логин 'admin', пароль 'password'
Положим, что для работы с одминкой достаточно иметь роль 'login', роль'admin' пока игнорируем.
Далее работаем с контроллером (шаблоны описывать не буду, все файлы доступны для скачивания)
Метод before()
в нем удобно инициализировать
$this->auth = Auth_Jelly::instance();
поскольку пригодится на каждой странице
и заполним
$this->template->menu = $this->_auto_nav();
$this->template->footer = View::factory('admin/footer');в футере у меня будет статистика
В реальном приложении удобно было бы сделать общий менеджер разрешенных/запрещенных для просмотра страниц и поместить его куда-нибудь в before(). Однако в данном случае для краткости изложения обойдемся проверкой в соответствующих методах.
action_login()
Поскольку страница не имеет смысла для залогиненного юзера, проверяем это с учетом наличия у посетителя роли 'login'
if ($this->auth->logged_in('login'))
{
Request::instance()->redirect('auth/index');
}Обратите внимание: здесь я использовал для проверки метод Jelly_Auth::logged_in($role = NULL), который пытается заавтологинить юзера, если его нет в сессии. На других страницах я использую для проверки метод Auth::get_user(), чтобы автоматически получить данные пользователя. Особенностью Auth::get_user() является отсутствие автоматической проверки роли, то есть ее нужно будет проверять явно. Соответственно, в моем коде наличие роли 'login' проверяется только при логине.
Логин осуществляется после проверки наличия $_POST:
if ($_POST)
{
$username = $_POST['username'];
$password = $_POST['password'];
// пока будем танцевать без автологина
if ($this->auth->login($username, $password, FALSE))
{
Request::instance()->redirect('admin/index');
} else {
$errors = array('Login or password incorrect');
}
}
action_index()
главная страница сугубо формальная, чтобы продемонстрировать факт успешного логина. Смотрим, залогинен ли посетитель, и если нет -- то редирект на страницу логина
$user = $this->auth->get_user();
if ( ! $user)
{
Request::instance()->redirect('admin/login');
}полученный объект $user используем для вывода приветствия
action_users($action = NULL, $id = NULL)
это самый "толстый" метод, собственно, в нем-то и можно немного пощупать сам Jelly. Здесь предусмотрим обработку событий
- просмотр списка юзеров (метод без параметров)
- добавление юзера ($action = 'add')
- редактирование данных юзера ($action = 'edit', $id = ID пользователя)
- удаление юзера ($action = 'del', $id = ID пользователя)
1) просмотр списка всех юзеров реализуется достаточно просто:
$users = Jelly::select('user')->execute();после чего переменная $users содержит массив найденных объектов.
Модель User у нас описана в модуле Jelly-Auth, поэтому дополнительных телодвижений пока не нужно (потом возможно захочется добавить еще полей, тогда уж придется наследовать от нее свою модель)
2) для добавления юзера извлекаем данные из $_POST (красивее сделать с помощью Arr::extract(), но для наглядности у меня прописано явно). Затем установим значения полей объекта Jelly::factory('user') и сохраним все это:
Jelly::factory('user')
->set(array(
'username' => $username,
'password' => $password,
'password_confirm' => $password_confirm,
'email' => $email,
'roles' => Jelly::select('role')
->where('name', '=', 'login')
->execute(),
))
->save();Заметьте, что мы заполняем и поле роли, выбрав нужные значения из таблицы ролей при помощи Jelly::select(). Посмотрим на выполненные запросы к БД:
SELECT `roles`.`id` AS `id`, `roles`.`name` AS `name`, `roles`.`description` AS `description` FROM `roles` WHERE `roles`.`name` = 'login'
SELECT COUNT(*) AS `total` FROM `users` WHERE `users`.`username` = 'testtt' ORDER BY `users`.`username` ASC
SELECT COUNT(*) AS `total` FROM `users` WHERE `users`.`email` = 'asdf@vxcvxc.com' ORDER BY `users`.`username` ASC<BR>INSERT INTO `users` (`username`, `password`, `email`, `logins`, `last_login`) VALUES ('testtt', 'ffe4126c4f790dbafca44c82a3793f6cea32feef87efe87cc1', 'asdf@vxcvxc.com', 0, NULL)
INSERT INTO `roles_users` (`user_id`, `role_id`) VALUES (5, 1)Два запроса с COUNT обусловлены требованием уникальности username и email (см. Model_Auth_User из Jelly-Auth), но зачем же COUNT(*), это же неэффективно? Хотя в общем все довольно логично.
Еще одна вещь, которая мне не нравится в Jelly-Auth, это выбрасывание Validation_Exception при ошибке валидации данных. Это простое решение, однако, например, в случае ошибки БД сложнее будет ловить то исключение.
3) для редактирования юзера я выбираю его из БД по первичному ключу (тогда не нужно писать условие в where())
$user = Jelly::select('user')
->load($id);затем проверив $_POST присваиваю нужные поля и сохраняю объект
$user->username = $_POST['username'];
$user->email = $_POST['email'];
if ($_POST['password'])
{
$user->password = $_POST['password'];
$user->password_confirm = $_POST['password_confirm'];
}
...
$user->save();Вообще, апдэйтить можно и без предварительной загрузки объекта -- создав его при помощи Jelly::factory('user'), присвоить поля и сохранить $user->save($id). Однако здесь мне нужно вывести поля для редактирования, поэтому без загрузки объекта не обойтись.
4) наконец, удаление юзера осуществляется в два этапа. Сначала запрашивается подтверждение, и только после этого происходит удаление. Причем для предотвращения CSRF пользовательский ID после подтверждения передается через POST (в данном случае явно излишне, но "на вырост" пригодится).
if (isset($_POST['ok']))
{
if (Jelly::factory('user')->delete($_POST['id']))
{
$this->template->content = 'User was deleted';
} else {
$this->template->content = 'User was not deleted';
}
} else {
Request::instance()->redirect('admin/users');
}action_logout()
тут все просто
$this->auth->logout();
Request::instance()->redirect('admin/login');Как видите, базовые операции реализуются достаточно просто. Однако очевидно, что приведенный вариант админки страдает многими недугами. Это и неразвитость ролевой системы (любой юзер может создавать/редактировать/удалять любого), отсутствие некоторых удобных фич типа автологина и небезопасность использования. Будем дорабатывать.
Скачать / Download 103 (zipped ~6 KiB)


Комментариев: 9
Тут на оф.форуме недавно появился код для аналогичных действий (http://forum.kohanaphp.com/comments.php?DiscussionID=5385&page=1#Item_3)
Честно говоря, сильно не вглядывался, но что это за защита от CSRF с помощью массива $_POST? Просто отправляется POST-запрос с параметрами id и ok, ведь в этом случае юзер удалится? Обычно генерируется token, который сохраняется в сессии и добавляется в форму в виде hidden-поля. После отправки контроллер сравнивает полученное значение токена и параметра в сессии.
]]>
Да, я сегодня утром на форуме увидел, сходные мысли приходят одновременно разным людям
Но у меня вроде пока побольше функционала, так что статью решил опубликовать.
Насчет CSRF -- уникальный токен это, несомненно, лучшее решение, но в некоторых простых случаях может помочь и POST вместо GET.
Сейчас никого POST'ом не испугать, к сожалению
Разве что простого пользователя, который может вбить руками URL для экспериментов, но не более.
]]>
Стандартный модуль ORM, по-моему, здесь ни к чему.
jelly-auth - расширение стандартного модуля auth.
Для работы с jelly - просто изменить в config/auth.php
драйвер на 'driver' => 'Jelly' ;)
]]>
biakaveron ну хоть против < img src = "http://site-where-you-are-admin.com/users/delete?filter=all" / > поможет, естественно против аяксовских постов такая медицина бессильна.
Sezarin хм, боюсь, что вы правы, я как-то и не подумал :( проверю, спасибо
Крайне "изумлен" некомпетентностью автора. Ч.г. не часто приходится встречаться.
]]>
Kohanetc
буду признателен, если вы укажете, в чем тут я некомпетентен. Стараюсь учиться у более опытных людей, поэтому рад указаниям на ошибки.
если неудобно здесь, буду признателен за пояснения по мылу (Alexander (dot) Kupreev (at) gmail (dot) com) или по аське 291688264 или джаббер 291688264@qip.ru. Спасибо
]]>
Не обращайте внимания, Александр (#comment-226).
Главное - не старайтесь выглядеть экспертом...
Экспертов читать не интересно - их блоги напоминают технические инструкции, зачастую даже вводящие читателя в заблуждение!
Продолжайте писать - у Вас хороший стиль изложения...
]]>
Спасибо, стараюсь