user-2-48x48Сегодня я расскажу вам об очень полезном и удобном компоненте для авторизации пользователей. Как известно в CakePHP входит готовый компонент Auth, который, фактически, является не самой понятной частью Cake’а. Благодоря ребятам из Studiocanaria процесс использования этого компонента стал значительно проще, понятней и удобней.

CakePHP Auth Component:

Важной частью стандартного компонента Auth для CakePHP является т.н. ACL - Access Control List. Возможно, он является удобным в тех или иных случаях, но сегодня мы будем говорить о более простой системе прав доступа созданной в Studio Canaria.

Основные черты системы:

  • Авторизация в системе при помощи родного для Cake компонента Auth
  • Создание групп пользователей
  • Использование текстовых описаний правил доступа к контроллерам и действиям вида controller:action, включая использование * для разрешения доступа ко всем подэлементам (т.е.controller:*).
  • Правила доступа кешируются в сессию (таким образом уменьшается количество обращений к базе данных)
  • Вместо простых идентификаторов типа int в системе используются UUID(Universally Unique Identifiers). Это удобно для использования в больших базах данных, в том числе при объединении нескольких баз (впрочем можно использовать и простые int-ключи)

Приступим к делу!

База данных

CREATE TABLE `groups` (
`id` char(36) NOT NULL,
`name` varchar(40) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `groups_permissions` (
`group_id` char(36) NOT NULL,
`permission_id` char(36) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `groups_users` (
`group_id` char(36) NOT NULL,
`user_id` char(36) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `permissions` (
`id` char(36) NOT NULL,
`name` varchar(40) NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `users` (
`id` char(36) NOT NULL,
`email_address` varchar(127) NOT NULL,
`password` varchar(40) NOT NULL,
`active` tinyint(4) NOT NULL default '0',
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

Модели

Модель User

app/models/user.php

class User extends AppModel {
    var $displayField = 'email_address';
    var $name = 'User';
    var $validate = array(
        'email_address' => array('email'),
        'password' => array('alphaNumeric'),
        'active' => array('numeric')
    );
    var $hasAndBelongsToMany = array(
            'Group' => array('className' => 'Group',
                        'joinTable' => 'groups_users',
                        'foreignKey' => 'user_id',
                        'associationForeignKey' => 'group_id',
                        'unique' => true
            )
    );
}

Модель Group

app/models/group.php

class Group extends AppModel {
    var $name = 'Group';
    var $hasAndBelongsToMany = array(
            'Permission' => array('className' => 'Permission',
                        'joinTable' => 'groups_permissions',
                        'foreignKey' => 'group_id',
                        'associationForeignKey' => 'permission_id',
                        'unique' => true
            ),
            'User' => array('className' => 'User',
                        'joinTable' => 'groups_users',
                        'foreignKey' => 'group_id',
                        'associationForeignKey' => 'user_id',
                        'unique' => true
            )
    );
}

Модель Permission

app/models/permission.php

class Permission extends AppModel {
    var $name = 'Permission';
    var $hasAndBelongsToMany = array(
            'Group' => array('className' => 'Group',
                        'joinTable' => 'groups_permissions',
                        'foreignKey' => 'permission_id',
                        'associationForeignKey' => 'group_id',
                        'unique' => true
            )
    );
}

Контроллеры

Контроллер Users

app/controllers/users_controller.php

class UsersController extends AppController {
    var $name = 'Users';
    var $scaffold;
    function login(){}
    function logout(){
        $this->Session->del('Permissions');
        $this->redirect($this->Auth->logout());
    }
}

Контроллер Groups

app/controllers/groups_controller.php

class GroupsController extends AppController {
    var $name = 'Groups';
    var $scaffold;
}

Контроллер Permissions

app/controllers/permissions_controller.php

class PermissionsController extends AppController {
    var $name = 'Permissions';
    var $scaffold;
}

Контроллер App

app/app_controller.php

Ниже я постарался перевести коментарии к контроллеру, где содержится основная логика.

class AppController extends Controller {
    /**
     * components
     * 
     * Array of components to load for every controller in the application
     * 
     * @var $components array
     * @access public
     */
    var $components = array('Auth');
    /**
     * beforeFilter
     * 
     * Это событие обрабатывается перед любым действием контроллера
     * 
     * @access public 
     */
    function beforeFilter(){
        //Устанавливаем поля для авторизации в компоненте Auth вместо тех, что идут по-умолчанию
        $this->Auth->fields = array('username'=>'email_address','password'=>'password');
        // Устанавливаем действия доступные без авторизации по всей системе
        $this->Auth->allow(array('display'));//В частности указываем, что на статические страницы /pages/* доступ будет открыт всегда (к примеру, для отображения главной)
        //Страница, на которую будут переходить пользователи после выхода из системы
        $this->Auth->logoutRedirect = '/';
        //Страница, на которую будет переходить пользователь после входа в систему
        $this->Auth->loginRedirect = '/';
        //Расширим компонент Auth при помощи действия isAuthorized
        $this->Auth->authorize = 'controller';
        //Разрешим доступ только тем пользователям чьи профили активны
        $this->Auth->userScope = array('User.active = 1');
        //Передаём компонент авторизации в страницы вида
        $this->set('Auth',$this->Auth->user());
    }
    /**
     * beforeRender
     * 
     * Это событие происходит перед тем, как страница отрисовывается
     *
     * 
     * @access public 
     */
    function beforeRender(){
        //Если пользователь авторизирован, то мы обрабатываем
        //список разрешенных для него действий
        if($this->Auth->user()){
            $controllerList = Configure::listObjects('controller');
            $permittedControllers = array();
            foreach($controllerList as $controllerItem){
                if($controllerItem <> 'App'){
                    if($this->__permitted($controllerItem,'index')){
                        $permittedControllers[] = $controllerItem;
                    }
                }
            }
        }
        $this->set(compact('permittedControllers'));
    }
    /**
     * isAuthorized
     * 
     * Вызывается компонентом Auth для проверки доступа к элементу
     * тут мы и будем проводить нашу проверку
     * 
     * @return true if authorised/false if not authorized
     * @access public
     */
    function isAuthorized(){
        return $this->__permitted($this->name,$this->action);
    }
    /**
     * __permitted
     * 
     * Вспомогательная функция, которая производит проверку прав пользователя
     * описанных в форме $controllerName:$actionName
     * @return 
     * @param $controllerName Object
     * @param $actionName Object
     */
    function __permitted($controllerName,$actionName){
        //Имя контроллеря указываем в нижнем регистре
        $controllerName = low($controllerName);
        $actionName = low($actionName);
        //Если в сессии права не были закешированны
        if(!$this->Session->check('Permissions')){
            //...то подготовим массив для сохранения
            $permissions = array();
            //у всех есть право выйти из системы
            $permissions[]='users:logout';
            //Импортируем модель пользователя, чтобы получить права
            App::import('Model', 'User');
            $thisUser = new User;
            //Получаем текущего пользователя и его группу
            $thisGroups = $thisUser->find(array('User.id'=>$this->Auth->user('id')));
            $thisGroups = $thisGroups['Group'];
            foreach($thisGroups as $thisGroup){
                $thisPermissions = $thisUser->Group->find(array('Group.id'=>$thisGroup['id']));
                $thisPermissions = $thisPermissions['Permission'];
                foreach($thisPermissions as $thisPermission){
                    $permissions[]=$thisPermission['name'];
                }
            }
            //Записываем права в сессию
            $this->Session->write('Permissions',$permissions);
        }else{
            //...видимо права закешированны, загружаем из сессии
            $permissions = $this->Session->read('Permissions');
        }
        //Ищем среди прав соотвествующее текущему
        foreach($permissions as $permission){
            if($permission == '*'){
                return true;//Найдено право СуперАдмина :)
            }
            if($permission == $controllerName.':*'){
                return true;//Разрешаются все действия в данном контроллере
            }
            if($permission == $controllerName.':'.$actionName){
                return true;//Найдено определённое действие
            }
        }
        return false;
    }
}

Вот в общем-то и всё, остались детали.

Страница входа

Чтобы система работала, в неё надо войти.

app/views/users/login.ctp

echo $form->create('User', array('action' => 'login'));
echo $form->input('email_address',array('between'=>'<br>','class'=>'text'));
echo $form->input('password',array('between'=>'<br>','class'=>'text'));
echo $form->end('Sign In');

Настройки системы

Во-первых надо создать первого пользователя и права. Для этого отправляемся в app_controller (/app/app_controller.php) и заменяем строку:
$this->Auth->allow(array(’display’));
на строку
$this->Auth->allow(array(’*'));

Предпложим, что ваш проект находится на http://localhost, тогда выполните следущие действия:

  • Переходим на http://localhost/permissions
  • Создаём новое разрешение и в качестве имени указываем *
  • Нажимаем New Group, создаём группу System developers (или на ваш вкус) с правами *
  • Нажимаем на New User, создаём пользователя так, чтобы поле Active было равно 1 (т.е. чекбокс выбран), а группа установленна в System developers

Теперь возвращаем app_controller в оригинальное состояние:
app_controller (/app/app_controller.php) и заменяем строку:
$this->Auth->allow(array(’*'));
на строку
$this->Auth->allow(array(’display’));

Вот и все, вы готовы.

Использование системы

Теперь вы можете создать столько разрешений, сколько вам будет угодно, и применить их к целой гамме разных групп на ваш вкус. Это могут быть разрешения вида controller:action, или controller:*

Если у вас будет желание, для неавторизированных пользователей можно добавить сколько угодно доступных действий изменяя app_controller:

function beforeFilter(){
    parent::beforeFilter();
    $this->Auth->allow('display','и','список','любых','других','действий','можно','указать','тут');
}

Таким же образом, можно использовать $permittedControllers для определения разрешённых всегда контроллеров.

Через переменную Auth можно определять авторизирован ли пользователь, а так же его свойства.
К примеру так:

  if ($this->Auth->user() {
     // Пользователь авторизирован
    echo $this->Auth->user("email_address");
  } else {
     echo "Анонимный пользователь";
 }

Обратите внимание на то, что через метод user() можно получать доступ ко всем полям таблицы Users.

Лёгкие подводные камни

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

     $user = $this->User->findById($this->Auth->user('id'));
     $this->Session->write($this->Auth->sessionKey, $user['User']);

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

Bookmark and Share
This entry was posted on Sunday, February 1st, 2009 and is filed under PHP-разработка, cakephp. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
free counters

Designed by Gabfire
Rambler's Top100