PHP framework CodeIgniter. Авторизация посетителей и ограничение доступа

Владимир | | CodeIgniter, PHP.

Авторизация посетителей

С вопросами безопасности web ресурсов постоянно сталкивается каждый разработчик. И, естественно, в этой области существует масса решений. В этой статье я хочу рассказать о встроенных возможностях фрэймворка CodeIgniter, которые можно использовать для авторизации посетителей и ограничения их доступа.

Прежде всего, вкратце обрисую сложившуюся ситуацию.

Большинство web ресурсов используют для защиты пару «имя — пароль». Это не самый безопасный вариант, но зато удобный (простой) в использовании.

Более сложные методы защиты требуют от посетителя либо специальных знаний, либо покупки оборудования, либо того и другого. Например, служба WebMoney использует сертификаты для авторизации пользователей. Метод хороший, но инструкция по использованию и безопасной работе с этими сертификатами занимает несколько страниц. Обычный пользователь интернета не станет связываться с такой системой без достаточно веской причины (в случае WebMoney речь идет о его собственных деньгах).

Об аппаратной защите (вроде биометрических датчиков и т.п.) и говорить не приходиться. Эти устройства стоят денег и, зачастую, немаленьких.

Поэтому, метод авторизации с помощью имени и пароля, похоже, останется самым распространенным в обозримом будущем.

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

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

CodeIgniter предлагает элегантное решение этой проблемы. Идея заключается в использовании метода _remap. Если в контроллере объявлен этот метод, то обращения к любому другому методу контроллера будут переадресованы ему.

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

function _remap($method) {
	//страницы, доступные без авторизации
	$allowedPages = array('index', 'newuser', 'about');
	$pars = $this->uri->segment_array();
	unset($pars[1]);
	unset($pars[2]);
	if (($method != null) &&
		(($this->session->userdata('username') != null) ||	in_array($method, $allowedPages))) {
		call_user_func_array(array($this, $method), $pars);
	}
	else {
		$this->index();
	}
}

Разберем подробнее код этого примера.

Прежде всего, я объявил массив ($allowedPages) с перечнем страниц (методов контроллера) доступных без авторизации. Перечень составлен произвольно и предполагается, что страница 'index' содержит форму для ввода имени и пароля.

После этого, сохранил все сегменты адреса и удалил первые два (строки 4-6). Как вы помните, в CodeIgniter первый сегмент адреса содержит имя контроллера (оно нам не нужно), второй – имя метода (передается методу _remap в параметре $method), в остальных могут передаваться параметры (вот их нужно сохранить и передать вызываемому методу).

В строках 7, 8 проверяется можно ли вызвать указанный метод. Приведенный код вызовет метод в двух случаях:
1) если в сессии был установлен параметр 'username';
2) название метода содержится в массиве $allowedPages, т.е. доступ к нему разрешен без авторизации.

Вызов метода контроллера осуществляется с помощью функции call_user_func_array, т.к. параметры метода находятся в массиве.

Если условия не выполняются, будет вызван index(). Т.е. посетитель попадет на главную страницу сайта с формой ввода имени и пароля.

Как видите, все довольно просто.

Важно. Если ваш сайт содержит несколько контроллеров, то метод _remap должен быть объявлен в каждом из них.

Ограничения

Описанный метод удобно использовать, если вам достаточно одного пользователя с административными правами.

В случае, если вам нужно создавать группы пользователей и назначать им различные привилегии, то, стоит попробовать одну из библиотек авторизации, разработанных специально для CodeIgniter. Например, FreakAuth, EzAuth, UserAuth.

Кроме того, можно использовать и другие библиотеки. Например, mihailt опубликовал очень интересную статью «Использование Zend_Acl для контроля доступа в Codeigniter’e».

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

Удачи!

  • Tazman

    Интересный метод. Вот только с привилегиями не очень удобно. У себя я сделал так: взял за основу simplelogin. Расширил для работы с группами, добавил 2 функции: что в return показывает выполнен ли вход, и что показывает группу пользователя и все норм. В каждой функции что требует авторицации или опред. группы (у меня всего три) я приписал проверку входа и группы (всего 4 строчки copy-paste) вроде работает 🙂

    • Идея как раз была в том, чтобы не писать код проверки во всех функциях.
      Хотя… «4 строчки copy-paste» конечно не проблема.

  • Tazman

    Интересный метод. Вот только с привилегиями не очень удобно. У себя я сделал так: взял за основу simplelogin. Расширил для работы с группами, добавил 2 функции: что в return показывает выполнен ли вход, и что показывает группу пользователя и все норм. В каждой функции что требует авторицации или опред. группы (у меня всего три) я приписал проверку входа и группы (всего 4 строчки copy-paste) вроде работает 🙂

    • Идея как раз была в том, чтобы не писать код проверки во всех функциях.
      Хотя… «4 строчки copy-paste» конечно не проблема.

  • Sam

    Некрасиво.

  • Sam

    Некрасиво.

  • Имхо в виде хука это выглядело бы элегантней 😎

    • Sam

      Не уверен…

  • Имхо в виде хука это выглядело бы элегантней 😎

    • Sam

      Не уверен…

  • В принципе тоже самое можно запихнуть и в конструктор или сделать хелпер в нём используя get_instance() и прописать в autoload

    для получения контроллера и его метода, лучше не обрабатывать сегменты урл, а использовать методы класса Router — fetch_class() и fetch_method()

    Erkana — вещь неплохая, правда очень сильно напоминает рапидовский Auth класс — не знаю как у них там и что но попахивает плагиатом

    Сейчас в основном использую Zend_acl и Zend_Auth чего и вам советую

    • Sam

      Как знал, что есть это там, но недостаточно порылся. Написал своё. В общем, огромное человеческое спасибо за fetch_method().

      • да не за что 😉

  • В принципе тоже самое можно запихнуть и в конструктор или сделать хелпер в нём используя get_instance() и прописать в autoload

    для получения контроллера и его метода, лучше не обрабатывать сегменты урл, а использовать методы класса Router — fetch_class() и fetch_method()

    Erkana — вещь неплохая, правда очень сильно напоминает рапидовский Auth класс — не знаю как у них там и что но попахивает плагиатом

    Сейчас в основном использую Zend_acl и Zend_Auth чего и вам советую

    • Sam

      Как знал, что есть это там, но недостаточно порылся. Написал своё. В общем, огромное человеческое спасибо за fetch_method().

      • да не за что 😉

  • Хотел узнать. Есть ли где-нибудь описанные решения по интеграции авторизации в CodeIgniter и популярными форумами (наиболее интересны SMF и phpBB2).

    Пожалуйста, поделитесь ссылочками 🙂

    • Честно говоря, вопрос поставил меня в тупик.

      Если я правильно понял, нужно прикрутить сайт (web службу) к форуму или наоборот. При этом если посетитель авторизировался на сайте, он должен автоматически авторизироваться на форуме.

      Мне кажется, что для решения этой задачи придется «плясать» от системы авторизации форума. Т.е. использовать общую таблицу пользователей, посмотреть какие параметры должны быть сохранены в сессии и т.п.

      Готовые решения может и существуют, но я с ними не сталкивался.

  • Хотел узнать. Есть ли где-нибудь описанные решения по интеграции авторизации в CodeIgniter и популярными форумами (наиболее интересны SMF и phpBB2).

    Пожалуйста, поделитесь ссылочками 🙂

    • Честно говоря, вопрос поставил меня в тупик.

      Если я правильно понял, нужно прикрутить сайт (web службу) к форуму или наоборот. При этом если посетитель авторизировался на сайте, он должен автоматически авторизироваться на форуме.

      Мне кажется, что для решения этой задачи придется «плясать» от системы авторизации форума. Т.е. использовать общую таблицу пользователей, посмотреть какие параметры должны быть сохранены в сессии и т.п.

      Готовые решения может и существуют, но я с ними не сталкивался.

  • Lester

    >Важно. Если ваш сайт содержит несколько
    >контроллеров, то метод _remap должен быть объявлен
    >в каждом из них.
    Сайт с одним контролером — это слишком примитивно, куда более интереснее было бы описать как организовать авторизацию на сайте с несколькими контроллерами. Я так мыслю, в этом случае надо задействовать хуки или расширять базовый контроллер. Только начинаю разбираться с CI, поэтому конкретных примеров привести пока не могу, если кто реализовывал такую авторизацию, поделитесь мыслями, примерами.

    • Чтобы сделать авторизацию на сайте с несколькими описанным методом достаточно скопировать метод _remap в конструктор каждого контроллера.
      Как правильно заметил Sam это не очень красивое решение, но оно работает.

      Еще советую попробовать библиотеки авторизации. Ссылки есть в статье и комментариях, кроме того mihailt была статья о подключении Zend_acl к CI.

  • Lester

    >Важно. Если ваш сайт содержит несколько
    >контроллеров, то метод _remap должен быть объявлен
    >в каждом из них.
    Сайт с одним контролером — это слишком примитивно, куда более интереснее было бы описать как организовать авторизацию на сайте с несколькими контроллерами. Я так мыслю, в этом случае надо задействовать хуки или расширять базовый контроллер. Только начинаю разбираться с CI, поэтому конкретных примеров привести пока не могу, если кто реализовывал такую авторизацию, поделитесь мыслями, примерами.

    • Чтобы сделать авторизацию на сайте с несколькими описанным методом достаточно скопировать метод _remap в конструктор каждого контроллера.
      Как правильно заметил Sam это не очень красивое решение, но оно работает.

      Еще советую попробовать библиотеки авторизации. Ссылки есть в статье и комментариях, кроме того mihailt была статья о подключении Zend_acl к CI.

  • go

    а почему
    call_user_func_array(array($this, $method), $pars);
    в упор не пойму!
    чем это отличается от
    $this->$method($pars);
    и красивее и короче да и логичнее, вообше не пойму зачем CI делает так же у себя в ядре, передавая управлени юзерскому приложению в codeigniter.php, там тоже есть такой способ, может кто нить объяснит зачем использовать ф-цию в замен стандаартного способа?

    • Для $this->$method($pars) нужно перечислить параметры метода как при обычном вызове, а call_user_func_array принимает массив с параметрами. Это удобнее если методы принимают разное количество параметров.

  • go

    а почему
    call_user_func_array(array($this, $method), $pars);
    в упор не пойму!
    чем это отличается от
    $this->$method($pars);
    и красивее и короче да и логичнее, вообше не пойму зачем CI делает так же у себя в ядре, передавая управлени юзерскому приложению в codeigniter.php, там тоже есть такой способ, может кто нить объяснит зачем использовать ф-цию в замен стандаартного способа?

    • Для $this->$method($pars) нужно перечислить параметры метода как при обычном вызове, а call_user_func_array принимает массив с параметрами. Это удобнее если методы принимают разное количество параметров.

  • go

    Владмир, а вам не кажется что это откоряка? чистой воды…
    во-первых указанный мною способ визуально логичнее для восприятия, а во-вторых вместо одного вызова вы делаете два! т.е. вначале вызов call_…. а потом эта функция наверняка делает вызов нужного метода… в результате вы используете больше памяти (не намного но больше).

    • Почему откоряка?
      Может памяти и больше потребляется, но и количество кода сокращается. CI позволяет получить параметры в виде массива. Если использовать $this->$method($pars), то сначала придется проверить сколько параметров нужно передать и написать что-то вроде:
      switch ($count) {
      case 1: $this->$method($pars[0]);
      case 2: $this->$method($pars[0], $pars[1]);
      ...
      }

      И заодно проверить сколько параметров принимает данный метод.
      Вобщем сделать все то, что скорее всего делает метод call_user_func_array.

  • go

    Владмир, а вам не кажется что это откоряка? чистой воды…
    во-первых указанный мною способ визуально логичнее для восприятия, а во-вторых вместо одного вызова вы делаете два! т.е. вначале вызов call_…. а потом эта функция наверняка делает вызов нужного метода… в результате вы используете больше памяти (не намного но больше).

    • Почему откоряка?
      Может памяти и больше потребляется, но и количество кода сокращается. CI позволяет получить параметры в виде массива. Если использовать $this->$method($pars), то сначала придется проверить сколько параметров нужно передать и написать что-то вроде:
      switch ($count) {
      case 1: $this->$method($pars[0]);
      case 2: $this->$method($pars[0], $pars[1]);
      ...
      }

      И заодно проверить сколько параметров принимает данный метод.
      Вобщем сделать все то, что скорее всего делает метод call_user_func_array.

  • go

    Если я вас првильно понимаю то переданный в call_user_func_array массив параметров в метод передадутся уже не в виде массива а так как нужно методу? если так то это действительно удобно соглашусь с вами.
    Хоия считаю что в методе на приеме можно это все отловить и усечь. Просто внешне согласитесь, ну совершенно привычней и логичней был бы обращение к методу классическим способом/

    а так вообще то _remap примнимает два параметра
    1. название метода
    2. массив параметров
    да в доках это не сказано, но зато в исходниках отчетливо видно что именно так и происходит.

    // Is there a "remap" function?
    if (method_exists($CI, '_remap'))
    {
    $CI->_remap($method,array_slice($URI->rsegments, 2));

    честно говоря я не пойму зачем в выше приведенном коде 5 и 6 строчки (в самом верху страницы)? и главное ведь данный код совершенно ни как не спасает если придет запрос на несуществующий метод )) !!
    поэтому придется либо юзать PHP5 ( я за!!! ) и там есть метод _call который сработает в случае если нет такого метода и тогда ваш вариант с call_user_func… как раз подойдет
    либо делать все равно switch
    ну либо фиксит CI и делать проверку на метод в CodeIgniter.php

    а я делал в своем проекте
    _remap и большой switch и вызывал в case'ах $this->$method($args[0]), к примеру,
    я ведь знаю что нужно моему методу )) в смысле какие параметры

    • Да, вы правильно поняли 🙂
      Отсечь и уловить — это дополнительный код, а, учитывая, что есть готовая функция, это дублирование кода.
      Согласен, обращение классическим способом выглядит лучше.

      >> принимает два параметра
      Признаю, просмотрел. Код лень было смотреть, ориентировался на доку. Спасибо!

      >> запрос на несуществующий метод
      Тут ситуация такая, если авторизированный посетитель обратиться к несуществующей странице, то действительно возникнет ошибка (как и при обычной работе фреймворка). А для не авторизированного посетителя в любом случае будет показана главная страница.

      >> юзать PHP5
      я не против, но почему бы не сделать просто страницу для ошибки 404 с навигацией по сайту?

      >> я ведь знаю что нужно моему методу
      конечно, знаете, но при добавлении новых методов придется вручную добавлять их в switch

  • go

    Если я вас првильно понимаю то переданный в call_user_func_array массив параметров в метод передадутся уже не в виде массива а так как нужно методу? если так то это действительно удобно соглашусь с вами.
    Хоия считаю что в методе на приеме можно это все отловить и усечь. Просто внешне согласитесь, ну совершенно привычней и логичней был бы обращение к методу классическим способом/

    а так вообще то _remap примнимает два параметра
    1. название метода
    2. массив параметров
    да в доках это не сказано, но зато в исходниках отчетливо видно что именно так и происходит.

    // Is there a "remap" function?
    if (method_exists($CI, '_remap'))
    {
    $CI->_remap($method,array_slice($URI->rsegments, 2));

    честно говоря я не пойму зачем в выше приведенном коде 5 и 6 строчки (в самом верху страницы)? и главное ведь данный код совершенно ни как не спасает если придет запрос на несуществующий метод )) !!
    поэтому придется либо юзать PHP5 ( я за!!! ) и там есть метод _call который сработает в случае если нет такого метода и тогда ваш вариант с call_user_func… как раз подойдет
    либо делать все равно switch
    ну либо фиксит CI и делать проверку на метод в CodeIgniter.php

    а я делал в своем проекте
    _remap и большой switch и вызывал в case'ах $this->$method($args[0]), к примеру,
    я ведь знаю что нужно моему методу )) в смысле какие параметры

    • Да, вы правильно поняли 🙂
      Отсечь и уловить — это дополнительный код, а, учитывая, что есть готовая функция, это дублирование кода.
      Согласен, обращение классическим способом выглядит лучше.

      >> принимает два параметра
      Признаю, просмотрел. Код лень было смотреть, ориентировался на доку. Спасибо!

      >> запрос на несуществующий метод
      Тут ситуация такая, если авторизированный посетитель обратиться к несуществующей странице, то действительно возникнет ошибка (как и при обычной работе фреймворка). А для не авторизированного посетителя в любом случае будет показана главная страница.

      >> юзать PHP5
      я не против, но почему бы не сделать просто страницу для ошибки 404 с навигацией по сайту?

      >> я ведь знаю что нужно моему методу
      конечно, знаете, но при добавлении новых методов придется вручную добавлять их в switch

  • go

    да про добавление в switch это действительно моментик ))
    кстати вот проект сделанный на CI — proday.by
    доска ВИДЕО объявлений
    (правда видео пока никто не добавил) =-)
    там для видео мы использовали youtube API…
    а нашем случае авторизацю вынесли в отдельный контроллер а в основном контроллере в конструкторе делаем простую проверку

  • go

    да про добавление в switch это действительно моментик ))
    кстати вот проект сделанный на CI — proday.by
    доска ВИДЕО объявлений
    (правда видео пока никто не добавил) =-)
    там для видео мы использовали youtube API…
    а нашем случае авторизацю вынесли в отдельный контроллер а в основном контроллере в конструкторе делаем простую проверку

  • Pingback: Simplelogin — простая авторизация для CodeIgniter » Статья » BlogerStyle()

  • Делал похоже как у вас, но весь код вынес в отдельную либу authorize и сделал ей автолоад.

    • Библиотека — правильное решение.
      Правда я последнее время пользуюсь готовыми.

  • Делал похоже как у вас, но весь код вынес в отдельную либу authorize и сделал ей автолоад.

    • Библиотека — правильное решение.
      Правда я последнее время пользуюсь готовыми.

  • Max

    А чем не нравится использование библиотеки в автолоаде? в конструкторе класса (так мы его инициализируем) делаем все нужные вещи с авторизацией — если надо — отправляем на индекс, на защащённую страницу, да хоть к чёрту на рога. При этом, совсем не обязательно теперь дублировать этот ремап в других классах (ни в одном классе, я бы даже так сказал 🙂 ). Вот и всё 🙂

    • Max

      чччёрт, я не первый такой умный, сорри, не дочитал каменты

  • Max

    А чем не нравится использование библиотеки в автолоаде? в конструкторе класса (так мы его инициализируем) делаем все нужные вещи с авторизацией — если надо — отправляем на индекс, на защащённую страницу, да хоть к чёрту на рога. При этом, совсем не обязательно теперь дублировать этот ремап в других классах (ни в одном классе, я бы даже так сказал 🙂 ). Вот и всё 🙂

    • Max

      чччёрт, я не первый такой умный, сорри, не дочитал каменты

  • Игорь

    При попытке запустить:
    Fatal error: Call to undefined function anchor() in Y:homeformwwwsystemapplicationviewsindex.php on line 7

    Несколько раз проверял, заново выполнял урок, искал ошибку — бесполезно.

    Владимир, не могли бы вы выслать мне на мыло, уже работающий урок в виде готового сайта? я тогда сравню файлы, и выявлю ошибку у себя?

    Или просто — добрый человек, у которого получается этот урок, — вышли мне на мыло архив с раб. сайтом урока на eretic83@mail.ru

    • Добавьте в конструктор контроллера
      $this->load->helper('url');

      или в файл applicationconfigautoload.php

      $autoload['helper'] = array('url');

      • Aurel

        Sposibo Vladimir za sovet u menya toje takaya oshibka pro anchor() vidovalo,a teperi otlychino!!!

  • Игорь

    При попытке запустить:
    Fatal error: Call to undefined function anchor() in Y:\home\form\www\system\application\views\index.php on line 7

    Несколько раз проверял, заново выполнял урок, искал ошибку — бесполезно.

    Владимир, не могли бы вы выслать мне на мыло, уже работающий урок в виде готового сайта? я тогда сравню файлы, и выявлю ошибку у себя?

    Или просто — добрый человек, у которого получается этот урок, — вышли мне на мыло архив с раб. сайтом урока на eretic83@mail.ru

    • Добавьте в конструктор контроллера
      $this->load->helper('url');

      или в файл application\config\autoload.php

      $autoload['helper'] = array('url');

  • Pingback: Руководства, статьи и заметки()

  • Pingback: Авторизация в Codeigniter - Erkana, DX Auth, Ion Auth | Заметки Лёвика()

  • sanyco

    є набагато простіший спосіб….

    ствоюєм конфіг файл /application/config/access.php

    в ньому прописуєм доступи:

    $config['access'] = array(

    'page'=>array(

    'index'=>array(

    'type'=>array(

    'all'
    )
    )
    )
    )

    де page назва кортролера, index назва метода а type перелік типів користувачів які мають доступ до даного методу в контролені.

    А дальше все просто: в конструкторі контроллера пишем таке:
    $this->load->config('access');- загружаєм конфіг файл
    $access = $this->config->item('access'); — вибираєм наш масив доступу
    if(isset($access[$this->router->class][$this->router->method])){
    тут перевіряємо на доступність типу…

    $access[$this->router->class][$this->router->method]['type'] — наш тип

    якщо задовільняє то $this->access = TRUE;
    }else{

    $this->access = FALSE;

    }

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