В своем первом проекте на Yii я не понимала, как работает магия авторизации. Вызываешь метод Yii::$app->user->login() и пользователь авторизован. А как же пароль? Где он передается, как проверяется? В каком классе вообще искать этот волшебный метод login()?
Сегодня я хочу подробно рассказать о том, как работает авторизация в Yii2. Почему в Yii2, а не в Yii? Потому, что если вы только начинаете изучать Yii, стоит сразу разбираться во второй ветке, а не в первой, которую скоро и вовсе перестанут поддерживать.
И так, что вообще из себя представляет Yii::$app->user? Yii::$app->user — это компонент приложения. Класс, отвечающий за идентификацию пользователя прописан в конфиге
'user' => [ 'identityClass' => 'app\models\User', 'enableAutoLogin' => true, ],
Этот класс уже сгенерирован по умолчанию в любом проекте Yii. И именно он вводит в заблуждение. Класс app\models\User является обычным объектом приложения, реализующим интерфейс \yii\web\IdentityInterface, те в обязательном порядке должен реализовать лишь следующие несколько методов:
public static function findIdentity($id) public static function findIdentityByAccessToken($token, $type = null) public function getId() public function getAuthKey() public function validateAuthKey($authKey)
А где же метод login() ? Дело в том, что класс app\models\User это лишь вспомогательный класс, а не сам компонент Yii::$app->user. Сам компонент создается в момент создания приложения, как часть его ядра. В методе preInit базового класса Application создаются все компоненты приложения. Для любого web приложения доступны следующие компоненты
'log' => ['class' => 'yii\log\Dispatcher'], 'view' => ['class' => 'yii\web\View'], 'formatter' => ['class' => 'yii\i18n\Formatter'], 'i18n' => ['class' => 'yii\i18n\I18N'], 'mailer' => ['class' => 'yii\swiftmailer\Mailer'], 'urlManager' => ['class' => 'yii\web\UrlManager'], 'assetManager' => ['class' => 'yii\web\AssetManager'], 'security' => ['class' => 'yii\base\Security'], 'request' => ['class' => 'yii\web\Request'], 'response' => ['class' => 'yii\web\Response'], 'session' => ['class' => 'yii\web\Session'], 'user' => ['class' => 'yii\web\User'], 'errorHandler' => ['class' => 'yii\web\ErrorHandler'],
Среди которых и user, который представляет собой экземпляр класса yii\web\User. И в этом классе и реализованы те самые таинственные методы login(), logout(), can() и другие.
Впрочем метод login() лишь сохраняет во внутренней переменной данные класса идентификации и дергает поведения на beforeLogin() и afterLogin(). Те вызывать данный метод нужно уже после того, как проверим пароль и права доступа. Этот метод не авторизует пользователя, а лишь делает его авторизованным.
С классами разобрались, теперь посмотрим на сам процесс авторизации в том виде, в котором он представлен в только что созданном приложении Yii2.
public function actionLogin() { if (!\Yii::$app->user->isGuest) { return $this->goHome(); } $model = new LoginForm(); if ($model->load(Yii::$app->request->post()) && $model->login()) { //ПОЛЬЗОВАТЕЛЬ УСПЕШНО АВТОРИЗОВАН. ТУТ ПИШЕМ СВОЙ КОД return $this->goBack(); } else { return $this->render('login', [ 'model' => $model, ]); } }
Если пользователь уже авторизован, те в приватной переменной $_identity класса yii\web\User уже есть данные, то просто перенаправляем его с этой страницы на главную.
Если же нет, то создаем экземпляр модели LoginForm и вызываем метод login класса LoginForm. Если этот этап прошел успешно — пользователь авторизован. После того как пользователь авторизован можно производить какие-либо действия над ним. К примеру привязать роль или назначить права, используя Yii::$app->user.
public function login() { if ($this->validate()) { return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); } else { return false; } } public function getUser() { if ($this->_user === false) { $this->_user = User::findByUsername($this->username); } return $this->_user; }
Если валидация прошла, то сделаем пользователя, найденного по username в методе getUser(), авторизованным. Особого внимания заслуживает валидация параметров класса LoginForm
public function rules() { return [ // username and password are both required [['username', 'password'], 'required'], // rememberMe must be a boolean value ['rememberMe', 'boolean'], // password is validated by validatePassword() ['password', 'validatePassword'], ]; }
Поле password проверяется методом validatePassword.
public function validatePassword($attribute, $params) { if (!$this->hasErrors()) { $user = $this->getUser(); if (!$user || !$user->validatePassword($this->password)) { $this->addError($attribute, 'Incorrect username or password.'); } } }
Если пользователь не найден или метод app\models\User->validatePassword() вернул null, валидация не пройдет и вернет ошибку.
public function validatePassword($password) { return $this->password === $password; }
Если вы храните пароль зашифрованным, это нужно учесть в данном методе, прежде чем сравнивать строки.
Надеюсь после прочтения прошлых двух частей статьи стало понятно, как работает авторизация в Yii2.
Хотела расписать, как сделать авторизацию на основе OpenID, но нашла готовое решение от отца-основателя Yii. Смотрим на github. Отличное расширение, поддерживающее на данный момент 10 сервисов, таких как Google, Yandex, Facebook и тп. Так же дающее возможность писать свои клиенты на основе базовых классов. Так что изобретать велосипед не буду, а лишь расскажу как связать авторизацию соц сервисов со своим приложением.
Как установить и использовать сервисы подробно расписано в ReadMe проекта yii2-authclient. Но что дальше?
После успешной авторизации вызывается метод, прописанный в настройках action auth
public function actions() { return [ 'error' => [ 'class' => 'yii\web\ErrorAction', ], ... 'auth' => [ 'class' => 'yii\authclient\AuthAction', 'successCallback' => [$this, 'successCallback'], ], ]; } public function successCallback($client) { $attributes = $client->getUserAttributes(); //АВТОРИЗАЦИЯ УСПЕШНО ПРОЙДЕНА. ПИШЕМ ТУТ СВОЙ КОД }
Авторизация на стороннем сервисе пройдена успешно, $client->getUserAttributes() вернет все данные пользователя, полученные от стороннего сервиса. Теперь если этого достаточно, можно идентифицировать пользователя:
public function successCallback($client) { $attributes = $client->getUserAttributes(); $user = User::setFromOpenID($attributes); Yii::$app->user->login($user); }
Нужно помнить, что класс User должен реализовывать IdentityInterface. В моем примере метод setFromOpenID присваивает параметры, полученные от стороннего сервиса к моему объекту user и метод login делает пользователя авторизованным.
Важно понимать, что метод successCallback вызывается сторонним сервисом асинхронно. Пользователь не видит вызова этого метода. Поэтому на данном этапе не нужно никаких редиректов или рендеринга представлений. После вызова successCallback произойдет редирект successUrl, который по умолчанию по умолчанию установлен в Yii::$app->getUser()->getReturnUrl(). И на этой странице уже можно использовать пользователя через Yii::$app->user.
Если помимо авторизации на стороннем сервисе вам нужно связать авторизованного пользователя со пользователем в своей БД, присвоить ему роли и права, то делать это нужно так же в successCallback. Если пользователь авторизован успешно на стороннем сервисе, но отсутствует в вашей базе, не нужно вызывать Yii::$app->user->login($user) . Вместо этого сохраним сообщение об ошибке и при редиректе на страницу отобразим его пользователю.
Надеюсь данная статьи поможем разобраться новичкам с авторизацией в Yii2. Мне самой такая статья очень бы пригодилась в начале знакомства с Yii 🙂
А можно листинг метода User::setFromOpenID() ?
Метод User::setFromOpenID() должен возвращать объект User. У меня он пытается найти юзера по логину, а если не нашел — то создает нового:
Дальше авторизовываем юзера:
Статья очень доходчивая, спасибо Вам огромное, теперь буду читать Вас постоянно.
Спасибо 🙂 Пишите в комментариях статьи на какие темы вам бы хотелось почитать.
Не работает ссылка вот тут «Хотела расписать, как сделать авторизацию на основе OpenID, но нашла готовое решение от отца-основателя Yii. Смотрим на github.»
Спасибо, исправила. Правильная ссылка: https://github.com/yiisoft/yii2-authclient
Вот в первом комментарии приводите код:
$login = $client->getId().’_’.$attributes[‘login’];
$user = static::findOne([‘login’ => $login]);
Но $attributes[‘login’] не существует же. Заметил что для каждого клиента идентификатор хранится под разными ключами. Как быть то в этом случае. switch писать?
Это нужно для точной идентификации юзера. Например в Facebook есть пользователь с логином Ann и в Яндекс есть такой юзер, но это уже другой человек. По сути у меня логин получился составной — имя-сервиса_логин. В user ищется пользователь и возвращается запись из моей таблицы, где id — первичный уникальный ключ.
Вот тут есть пример, как с помощью промежуточной таблицы связать разные логины с разных сервисов с одним юзером.
Был не прав. Все таки уникальный id возвращается под ключом id.
Спасибо! Как раз сейчас с аутентификацией, авторизацией, рбак разбираюсь!