BugTracker: авторизация и аутентификация (часть восьмая)

22 марта, 2009
bug_tracker_logo_part8

Приветствую всех читателей!

В этой части мы рассмотрим создание не сложной системы авторизации пользователей нашего баг трекера.

Для начала сформулируем задачу.

Как вы, наверное, помните, оставить сообщение о найденном баге или комментарий к нему может кто угодно. Но удалять записи и комментарии могут только администраторы.

На практике это будет выглядеть следующим образом. Администратор при входе вводит имя и пароль. Баг трекер их проверяет и, если они совпадают с записанными в базе данных, добавляет к каждому багу и комментарию ссылку «Удалить».

Кроме того, при выполнении каждой операции удаления необходимо проверить является ли текущий пользователь администратором.

Для справки. Эти проверки называются аутентификация и авторизация.

Авторизация (англ. authorization) — процесс предоставления определенному лицу прав на выполнение некоторых действий.

Аутентификация (англ. Authentication) — процедура проверки соответствия некоего лица и его учетной записи в компьютерной системе.

(цитаты из википедии)

Таким образом, при входе в систему нам нужно аутентифицировать администратора, а при выполнении им операции – авторизовать.

Задача, в общем-то, стандартная и для её решения написано множество библиотек разной степени сложности.

Прежде чем переходить к выбору библиотеки, перечислим функции, которые нужны для разделения прав в нашем баг трекере.

1) Создание учетной записи в базе данных.

2) Проверка имени и пароля при входе в систему (аутентификация).

3) Проверка прав пользователя на выполнение операции (авторизация).

Требования минимальные и не сложно было бы реализовать такую систему самостоятельно. Но зачем «изобретать велосипед» если есть куча готовых решений?

Для этого проекта я решил использовать библиотеку под названием Redux. Каких-то особых причин, по которым я на ней остановился, нет. Просто видел несколько хороших отзывов, и захотелось поэкспериментировать.

На данный момент есть две версии библиотеки: 1.4а (стабильная) и 2 (бета). Вторая по своим возможностям мне понравилась больше, поэтому и использовал я именно её. Но бета есть бета, и сразу обнаружилось несколько недостатков.

Самое главное, в архиве 2-ой версии нет документации, есть только демонстрационный пример. Полноценная справка написана для версии 1.4, причем очень хорошая, сделана в том же стиле, что и тьюториал CodeIgniter.

Используя пример и справку из версии 1.4, разобраться не сложно.

Теперь рассмотрим, что нужно сделать для подключения этой библиотеки к нашему приложению.

1) Скачать и распаковать архив.

2) Скопировать 3 файла:
\application\config\redux_auth.php
\application\models\redux_auth_model.php
\application\libraries\redux_auth.php

3) Импортировать файл database.sql в базу данных. При этом будут созданы несколько таблиц.

4) Настроить библиотеку. Для этого, открываем файл \application\config\redux_auth.php и изменяем соответствующие значения. Здесь я только отключил активацию по email.

  1. $config['email_activation'] = false;

5) Создаем группу, которая используется по-умолчанию. В файле \application\config\redux_auth.php есть параметр

  1. $config['default_group'] = 'member';

Чтобы библиотека нормально заработала нужно создать в таблице groups запись с название этой группы. Т.е. name='member', description='member group description'.

6) В таблице sessions изменяем сравнение для поля user_data на utf8_general_ci, иначе будут проблемы с кириллицей.

7) Открываем файл \application\config\config.php и находим раздел с настройками сессий. Я изменил

  1. $config['sess_encrypt_cookie']  = TRUE;
  2. $config['sess_use_database']    = TRUE;
  3. $config['sess_table_name']      = 'sessions';
  4. $config['sess_match_ip']        = TRUE;
  5. $config['sess_match_useragent'] = TRUE;

8 ) Добавляем библиотеку в автозагрузку (файл \application\config\autoload.php).

  1. $autoload['libraries'] = array('database', 'session', 'redux_auth');
  2. $autoload['model'] = array('redux_auth_model');

Теперь нужно создать учетную запись администратора.

Т.к. в базе данных храниться не сам пароль, а его хеш, то удобнее всего добавить запись средствами библиотеки, а не вручную.

Для этого, добавим в контроллер (bugtracker) метод register.

  1. function register() {
  2.     $this->redux_auth->register('admin', 'password', 'admin@bugtracker.local');
  3. }

Заходим один раз на страницу bugtracker.local/bugtracker/register и в базе данных появляется новая запись.

После этого метод можно удалить.

Обратите внимание! Для аутентификации Redux использует не имя (admin), а email.

Создаем страницу с формой входа.

Для этого добавляем в контроллер метод login.

  1. function login() {
  2.     if ($this->redux_auth->logged_in()) {
  3.         redirect('bugtracker/page');
  4.     }
  5.     $pageData['title'] = 'Login';
  6.     $this->load->view('header', $pageData);
  7.     $this->load->view('login');
  8.     $this->load->view('footer');
  9. }

Этот метод просто создает страницу с формой. Сама форма находится в представлении \views\login.php.

Перед формированием страницы мы с помощью метода logged_in() проверяем, выполнил ли вход данный пользователь (строки 2-4). И если да, то отправляем его на главную страницу багтрекера.

Теперь рассмотрим представление (\views\login.php)

  1. <?php
  2. $mes = $this->session->flashdata('message');
  3. if (!empty($mes)) {
  4.     echo '<div id="infoMessage">'.$mes.'</div>';
  5. }
  6. ?>
  7. <?php echo form_open('bugtracker/checklogin', array('id'=>'fLogin')); ?>
  8.     <p>
  9.     <label for="email">eMail</label>
  10.     <input type="text" name="email" id="email" value="<?php echo set_value('email'); ?>" />
  11.     </p>
  12.     <?php echo form_error('eMail'); ?>
  13.     <p class="even">
  14.     <label for="password">Пароль</label>
  15.     <input type="password" name="password" id="password" value="" />
  16.     </p>
  17.     <?php echo form_error('password'); ?>
  18.     <p><input type="submit" name="bLogin" id="bLogin" class="bLogin" value="Войти" />
  19.     </p>
  20. </form>

Тут все просто. Мы создаем обычную форму с двумя полями: «eMail» и «Пароль». После нажатия на кнопку «Войти» данные будут отправлены методу checklogin контроллера (строка 7).

Этот метод выполняет аутентификацию пользователя. Проще говоря проверяет соответствие email'а паролю.

  1. function checklogin() {
  2.     $this->load->library('form_validation');
  3.     $this->form_validation->set_rules('email', 'lang:email', 'required|valid_email');
  4.     $this->form_validation->set_rules('password', 'lang:password', 'required');
  5.     $this->form_validation->set_error_delimiters('<div class="fErrMessage">', '</div>');
  6.    
  7.     if ($this->form_validation->run() == false)
  8.     {
  9.         $pageData['title'] = 'Login error';
  10.         $this->load->view('header', $pageData);
  11.         $this->load->view('login');
  12.         $this->load->view('footer');
  13.     }
  14.     else
  15.     {
  16.         $login    = $this->input->post('email');
  17.         $password = $this->input->post('password');
  18.        
  19.         if ($this->redux_auth->login($login, $password)) {
  20.             $this->session->set_flashdata('message', 'Привет, '.$login);
  21.             redirect('bugtracker/page');
  22.         }
  23.         else {
  24.             $this->session->set_flashdata('message', 'Неверная пара логин/пароль, попробуйте ещё раз');
  25.             redirect('bugtracker/login');
  26.         }
  27.     }
  28. }

Прежде всего, мы поверяем корректность заполнения формы. Для этого создаем правила (строки 3, 4) и выполняем проверку (сторока 7). Если форма заполнена правильно (данные введены), то с помощью метода login($login, $password) (строка 19) ищем посетителя в базе данных.

Если соответствующая запись в БД будет найдена, отправляем его на главную (строка 21). При этом библиотека сама сохранит данные этого пользователя в сессии. И в дальнейшем мы сможем использовать метод logged_in() для авторизации пользователя.

Если email или пароль введены не правильно, то показываем посетителю форму ещё раз с предложением попробовать ещё раз.

Теперь рассмотрим метод удаления записи о баге.

  1. function deletebug($bugId) {
  2.     if ($this->redux_auth->logged_in()) {
  3.         if ($this->mbug->delete($bugId)) {
  4.             $this->session->set_flashdata('message', 'Баг удален');
  5.             redirect($this->session->userdata('prev_page'));
  6.         }
  7.         else {
  8.             $this->session->set_flashdata('message', 'При удалении бага возникла ошибка');
  9.             redirect($this->session->userdata('prev_page'));
  10.         }
  11.     }
  12.     else {
  13.         $this->session->set_flashdata('message', 'Вы должны войти для того, чтобы выполнить это действие');
  14.         redirect('bugtracker/login');
  15.     }
  16. }

Во второй строке мы выполняем авторизацию пользователя. И в зависимости от её результатов либо удаляем баг (строки 3-10), либо отправляем посетителя на страницу с формой входа (строки 13, 14).

Т.е. если кто-то, не выполнив вход, попробует отправить запрос bugtracker.local/bugtracker/deletebug/1, то он увидит сообщение 'Вы должны войти для того, чтобы выполнить это действие' и форму ввода email и пароля.

В шаблонах багов и комментариев (bug_tpl.php и comment_tpl.php) также добавим несколько строк:

  1. if ($this->redux_auth->logged_in()) {
  2.     echo '<p class="deleteBug">'.anchor('bugtracker/deletebug/{id}', 'Удалить').'</p>';
  3. }

Таким образом, администраторы увидят ссылку «Удалить» рядом с каждым багом и комментарием.

Примечание. Подробнее об этих шаблонах можно почитать в четвертой части.

И последний момент. Нужно дать возможность администратору выйти из приложения.

Для этого создаем метод logout.

  1. function logout()
  2. {
  3.     $this->redux_auth->logout();
  4.     $this->session->set_flashdata('message', 'Вы вышли из администраторского режима');
  5.     redirect('bugtracker/page');
  6. }

После выхода (строка 2) мы сохраняем в сессии сообщение и отправляем редирект на главную страницу.

Как видите, библиотека довольно удобная и работать с ней не сложно. Кроме того, при необходимости с её помощью можно легко добавить возможность регистрации в баг трекере или разделить пользователей на группы с разными правами.

P.S. Если возникли вопросы или есть замечания, пишите, постараюсь ответить ;)

Понравилась статья? Подписывайтесь на продолжение rss link !

Или на мой твиттер twitter link

]]>

Добавьте эту страницу в google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

]]>

Опубликовано в CodeIgniter, MySQL, PHP, Web разработка View Comments

]]>
  • А напоминание пароля (если забыл вдруг) как сделать ?
  • Для этих целей в библиотеке предусмотрен метод forgotten_password
    Ему нужно передать email (в первом параметре) на который будет отправлено письмо.

    Т.е. нужно добавить метод в контроллер, который создаст страницу с формой восстановления пароля (если нужна) и вызовет forgotten_password
  • Castro
    Владимиh, спасибо за быстрый ответ. Извините за то, что пишу невпопад возможно, 5й день изучаю фреймворк и очень много вопросов накопилось.
    В данный момент хотел бы задать 2 вопроса:
    1) Как строить приложения, в которых есть больше чем 2 контролера? Допустим, в контролере Admin я хочу реализовать админку, а в контролере Blog собственно блог. Смогу ли я вызывать методы одного контроллера в другом или я не правильно планирую архитектуру приложения? Где вообще можно почитать понятно о том как проэктировать приложение?
    2) НЕ смог разобраться с тем, как сделать форму для редактирования данных из базы. Вроде идея проста - одним запросом вытягиваем данные, вставляем их в поля, а потом после сабмита, вызываем метод, который вставит всё это. ТЕ примеры что я видел на CI какие-то навороченные, с валидацией и не проверить никак работает ли оно. НЕту ли у вас какого-то несложного примера или ссылки на рассматриваемую тему?
  • 1) Из одного контроллера другой вы вызвать не сможете. Но есть решение - Modular Extensions.
    Проектирование тема большая и сложная :) Очень многое зависит от назначения приложения.
    В принципе, MVC - это тоже архитектура.
    2) По-моему в CI Tutorial все хорошо расписано. А валидация нужна обязательно.
    Например, в предыдущей части был подходящий пример.
  • Castro
    Владимир, спасибо. HMVC - это, похоже то, что нужно, хотелось именно модульности.
    Ещё пара вопросов.
    1) Первый вопрос немного касается Redux.
    Задача была в том, чтобы ограничить действия для групы пользателей. Сделал фунцию, которая проверяет роль пользователя и если он не админ, то ссылка не показывается. Но тут ведь есть простая уязвимость - можно ж просто сформировать урл и функция выполниться. Отсюда вопрос - где нужно ещё проверять роль: в контроллере или же в модели. Я вначале решил что в контролере, но с другой стороны в модели должна реализоваться всё бизнесс-логика. Как же будет правильно?
    2) Второй вопрос. Вызывать методы модели во вьюшке - зло?
  • 1) Проверять доступ нужно в контроллере. Иначе не получится закрыть доступ к методам. Создать массив с именами доступных методов и если посетитель не авторизовался, давать доступ только к ним. Я согласен бизнес логику нужно перенести в модель, но в данном случае никто не гарантирует, что модель будет использоваться в каждом методе контроллера. К тому же нужно заблокировать доступ до того как начнет выполняться код закрытого метода контроллера. Саму проверку можно выполнять в модели. Только метод проверки пользователя этой модели должен вызываться, например, в конструкторе контроллера.

    2) Не обязательно. Если верить википедии, это вполне допустимо. На на этой диаграмме вообще только представление обращается к модели (контроллер только изменяет состояние модели).
  • Castro
    И ещё вопрос. Решил вспомагательные функции написать. Но не знаю куда их прицепить теперь. Пока что создал отдельный хелпер куда внёс
    if ( ! function_exists('getCurrentUser'))
    {
    function getCurrentUser(){
    return $this->session->userdata('currentUser');
    }
    }
    if ( ! function_exists('setCurrentUser'))
    {
    function setCurrentUser($user){
    $this->session->set_userdata('currentUser', $user);
    }
    }

    может в Codeigniter єто можно как-то иначе решить?
  • Да, группы в redux - это и есть роли. Я их не использовал, т.к. пример демонстрационный, т.е. упрощенный :)

    В CI вы можете создавать хелперы или библиотеки с нужными функциями (обычно в библиотеках размещают классы). Правда функции которые вы привели состоят из одной строки, поэтому я не уверен, что они нужны (если вы добавите проверку введенных данных - ситуация изменится).

    Кроме того, если исходить из назначения функций (работа с данными пользователя), то логично будет добавить их в модель Users.
  • Castro
    Отличная статья!
    У меня есть ещё один вопрос. Нужно дать возможность пользователям сайта изменять только свои данные, а админу изменять и удалять даные любых пользователей.
    Как вообще вот так разделение сделать и как проверять принадлежность профайла конкретному пользователю?
  • Чаще всего используются т.н. роли. Их поддержка есть во многих библиотеках.
    Т.е. добавляется еще одно поле в базу данных в таблицу пользователей, а в php скриптах ставится проверка, например,
    if ($role === 'admin') {
    //выполняем действие
    }
    else {
    //доступ запрещен
    }
    Роль текущего пользователя можно получить из базы и сохранить, например, в сессии.
  • Castro
    Да-да.
    Я примерно так и предполагал.
    Но разве группы в redux - это не есть роли?
  • <<<И, честно говоря, выбрать довольно сложно, у многих библиотек похожие функции, а идеального решения не существует
    Верно,тут к сожалению и советы лучше не читать,т.к. каждый выбирает по своему.
  • Андрей
    Смущает ваш подход к использованию view. Почему не используете layout?
    $this->load->view('header', $pageData);
    $this->load->view('login');
    $this->load->view('footer');
    Очень некрасиво получается...

    Написав свою либу, у меня получается так:
    $this->view->set($pageData);
    А дальше все происходит автоматом, и не нужно ничего вызывать..
  • Интересный вопрос. Я использую стандартный подход CI (во всяком случае в CodeIgniter User Guide загрузка представлений выполняется именно так).

    Насколько я понял, в вашем варианте формирование страницы выполняется одним методом set. Но страницы в баг трекере разные. На каких-то есть форма добавления бага, на её нет, но есть форма добавления комментария. Я решаю проблему просто указанием нужных представлений при вызове view.

    У вас метод set кроме данных для формирования страницы никаких параметров не получает. Значит вам нужно где-то раньше указать какую страницу вы формируете (задать перечень блоков и их размещение). Т.е. использовать что-то вроде:
    $this->view->setLayout(...)
    или задавать их как-нибудь по-другому, например, в конфиге.

    Вариант с конфигом выглядит, конечно, красивее, чем последовательный вызов view. А setLayout по-сути тоже самое что и view.

    Отсюда вопрос: как вы указываете типы страниц в вашей библиотеке?
  • Андрей
    Все происходит так:
    есть общий layout, шаблон всех страниц.
    Моя либа сама определяет какой сейчас вызван контроллер и метод, и автоматом рендерит его. То есть если урл /page/about , controller = page, action = about, то моя либа будет искать файл /views/page/about.php. Отрендерит его, а потом подставит в /views/layout.php (в этом файле есть переменная $content, куда все и подставляется).

    Естественно, если нужно, можно отключить авто-рендер или рендерить вручную указаные файлы(если например используем аякс-запрос, где layout не нужен).
    Если нужно изменить layout, используем $this->view->setLayout('another_layout').
    Так же часто в самых layout нужно повесить так называемые виджеты (облако тегов, форма логина и т.д.). Для таких вещей используется либа Widget, в которой каждый виджет - это метод.

    Но сам я перешел на Kohana.) За время работы с CI и Kohana много изучил, написал для своей удобности. Возможно когда появится время, выложу все на хабр, я считаю есть чем поделится.
  • Получается, что наибольший эффект от такой библиотеки будет если на всех страницах повторяются все блоки кроме одного (который определяется текущим методом).

    Но для этого баг трекера такой вариант не очень подойдет. Например, на странице с общим списком багов выводится форма добавления комментария и сам список, а на странице с описанием выбранного бага - баг с комментариями и форма добавления комментария. Т.е. два отличающихся блока.

    Наверное самым удобным вариантом в таком случае будет создать перечень блоков в конфиге отдельно для каждого метода.
  • Big_Shark
    Я привел пример где сайт требует невзрачной авторизации для написания комментариев.
  • be3
    Владимир, а с библиотекой DX Auth работали? Хорошая документация и базовые примеры достаточно объемные.
  • Нет, не работал. Я ориентировался на этот список (в комментариях). И, честно говоря, выбрать довольно сложно, у многих библиотек похожие функции, а идеального решения не существует ;)
    В любом случае, спасибо за совет!
  • Terry
    Очень интересная серия статей. Все собираюсь освоить CodeIgniter ... но пока работаю своими методами.
    Если можно вопрос по авторизации: нужно ли в сессии хранить логин и пароль, и при каждом входе проверять их в БД? Или достаточно одан раз проверять данные, записывать в сессию флаг (например с айди юзера), и потом смотреть: если флаг есть - то авторизирован пользователь, нет - не авторизирован.

    И вопрос по вашему примеру:
    $config['sess_table_name'] = 'sessions';
    $config['sess_match_ip'] = TRUE;
    Зачем используеться таблица sessions ?
    И что значит sess_match_ip ?
  • Своими методами - это собственный фреймворк?

    нужно ли в сессии хранить логин и пароль


    Нет, не нужно. Достаточно один раз выполнить проверку и сохранить id в сессии. В данном случае эту операцию делает библиотека.

    Зачем используеться таблица sessions


    Как уже ответитил Big_Shark - для хранения данных сессии в БД. Дело в том, что встроенная библиотека сессий CI использует собственную реализацию сессий (не встроенную PHP), и если не использовать БД все данные сессии будут храниться на стороне клиента, а это не безопасно.

    sess_match_ip


    Вообще - это отдельная тема. В некоторых случаях эту проверку нужно отключать. Например, если посетитель не имеет статического IP (а это большинство пользвателей интернета), то возможно им придется заново вводить имя и пароль при каждом подключении к инету (IP адреса обычно выдаются из пула провайдером).
  • Big_Shark
    sess_match_ip необходимо включать для админ панели или каких либо сайтов на которых храниться закрытая информация пользователя.
    А допустим для того чтобы просто пользователь смог писать комментарии это лишние.
  • Пользователь может писать комментарии вообще без авторизации. Для него в сессии сохраняется только адрес текущей страницы, чтобы вернуть его на неё после отправки сообщения.
  • Big_Shark
    Если идет сверка информации с кук с базой то безопастность возрастает так как становиться невозможный подделать куку.
    $config['sess_match_ip'] означает что кука привязываеться к IP адресу компа с которого была создана.
blog comments powered by Disqus ]]>