vorst.ru - Статьи о задачах возникающих при создании сайта и их решении
Статьи помеченные oauth2

Рефакторинг кода авторизации

Использование аккаунта социальной сети для авторизации

Авторизация с помощью социальной сети не должна зависеть от типа аккаунта. В предыдущем посте используется код, где в конструкторе классa common/components/SocialContact.php выполняется оператор switch. При добавлении обработчика для новой социальной сети, придется дописывать код, при этом меняя класс.

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


Как подключить авторизацию через аккаунт

OAuth2 авторизация с использованием аккаунта

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

Авторизация с помощью социальной сети позволяет избежать повторного заполнения формы регистрации. При этом защищенность вашего аккаунта в социальной сети никак не страдает. Вы просто подтверждаете, что вы это вы. Как подключить эту услугу к проекту используя расширение yiisoft/yii2-authclient?


Как работает аутентификация OAuth2

OAuth2 аутентификация на примере Google

Если у вас есть аккаунт в Google, то вы наверняка пользовались документами. Например, электронными таблицами.

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

Можно просматривать список таблиц и редактировать выбранную таблицу оставаясь в рамках своего приложения. Примерно так - http://sample.vorst.ru/googl



Поиск



Рефакторинг кода авторизации

Использование аккаунта социальной сети для авторизации

  • 1. Как подключить авторизацию через аккаунт
  • 2. Рефакторинг кода авторизации

Авторизация с помощью социальной сети не должна зависеть от типа аккаунта. В предыдущем посте используется код, где в конструкторе классa common/components/SocialContact.php выполняется оператор switch. При добавлении обработчика для новой социальной сети, придется дописывать код, при этом меняя класс.

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


    Поделиться

Convertor

После запроса социальной сети программа получает объект $client. Метод getId() объекта возвращает строку "yandex", "vkontakte" и прочее. Можно использовать эту строку в качестве имени класса, который и будет выполнять необходимый код. Классы можно хранить в подкаталоге, например common/components/convertor/Yandex.php.

    public function __construct($client, $config = [])
    {
        $client_id = $client->getId();
        $attributes = $client->getUserAttributes();
        $this->id = (string)$attributes['id'];
        // convert from individual to a single view of attributes
        $class_name = 'common\\components\\convertor\\' . 
            ucfirst($client_id);
        $convertor = new $class_name();
        $convertor->set($this, $attributes);
        // if email not setted then make it
        if (!$this->email)
            $this->email = "{$attributes['id']}@{$client_id}.net";
        parent::__construct($config);
    }

Собственно сам конвертор, например common/components/convertor/Yandex.php должен реализовать метод set.

namespace commmon\components\convertor;
class Yandex implements Convertor {
    public function set($obj, $attributes)
    {
        $obj->name = $attributes['first_name'] . ' ' . $attributes['last_name'];
        $obj->email = isset($attributes['default_email'])
            ? $attributes['default_email']
            : false;
    }
}

SocialLink

При первом входе с помощью аккаунта социальной сети добавляется запись в модель User. Добавленная запись связывается с социальной сетью. Для этого создается запись в модели SocialLink.

Если же пользователь уже вошел с помощью имени и пароля, а потом нажал еще и на кнопку социальной сети, то можно связать существующую запись в модели User, добавив запись в модель SocialLink.

Таким образом действие выполняется два раза, поэтому стоит выделить его в отдельный метод. Полный текст common/components/SocialContact.php теперь следующий.

namespace common\components;
use Yii;
use yii\base\BaseObject;
use yii\base\InvalidValueException;
use common\models\SocialLink;
use common\models\User;
class SocialContact extends BaseObject
{
    public $id;
    public $name;
    public $email;
    private $_link;
    public function __construct($client, $config = [])
    {
        $client_id = $client->getId();
        $attributes = $client->getUserAttributes();
        $this->id = (string)$attributes['id'];
        // convert from individual to a single view of attributes
        $class_name = 'common\\components\\convertor\\' .
            ucfirst($client_id);
        $convertor = new $class_name();
        $convertor->set($this, $attributes);
        // if email not setted then make it
        if (!$this->email)
            $this->email = "{$attributes['id']}@{$client_id}.net";
        parent::__construct($config);
    }
    public function registration($client_id)
    {
        if (User::find()->where(['email' => $this->email])->exists()) {
            Yii::$app->getSession()->setFlash('error', [
                Yii::t('app', 
                    'User with {email} have been exist, but not linked to {client}. ' . 
                    'Try to login with other social network or with name and password.', [
                        'email' => $this->email,
                        'client' => $client_id,
                ]),
            ]);
        } else {
            if(User::find()->where(['name' => $this->name])->exists()) {
                Yii::$app->getSession()->setFlash('error', [
                    Yii::t('app', 
                        'A user named {name} already exists. ' .
                        'Try logging in if you have registered before.', [
                            'name' => $this->name,
                    ]),
                ]);
            } else {
                $password = Yii::$app->security->generateRandomString(6);
                $user = new User([
                    'name' => $this->name,
                    'email' => $this->email,
                    'password' => $password,
                    'status' => User::STATUS_ACTIVE,
                ]);
                $user->generateAuthKey();
                $user->generatePasswordResetToken();
                $transaction = $user->getDb()->beginTransaction();
                if ($user->save()) {
                    if ($this->makeLink($client_id, $user->id)) {
                        $transaction->commit();
                        Yii::$app->user->login($user);
                    } else {
                        throw new InvalidValueException($this->showErrors($this->_link)); 
                    }
                } else {
                    throw new InvalidValueException($this->showErrors($user)); 
                }
            }
        }
    }
    public function makeLink($client_id, $user_id)
    {
        $this->_link = new SocialLink([
            'user_id' => $user_id,
            'source' => $client_id,
            'source_id' => $this->id,
        ]);
        return $this->_link->save();
    }
    private function showErrors($model)
    {
        $out = 'Can\'t save ' . $model->tableName() . "\n";
        foreach($model->getErrors() as $field => $messages) {
            $out .= "«{$field}»\n";
            foreach($messages as $message) {
                $out .= "{$message}\n";
            }
            $out .= "\n";
        }
        return $out;
    }
}

Теперь действие авторизации в контроллере frontend/controllers/SiteController.php стало проще и понятнее, а значит мы достигли цели :).

    public function onAuthSuccess($client)
    {
        $social_contact = new SocialContact($client);
        $social_link = SocialLink::find()->where([
            'source' => $client->getId(),
            'source_id' => $social_contact->id,
        ])->one();
        if (Yii::$app->user->isGuest) {
            if ($social_link) { // authorization
                Yii::$app->user->login($social_link->user);
            } else { // registration
                $social_contact->registration($client->getId());
            }
        } else { // the user is already registered
            if (!$social_link) { // add external service of authentification
                $social_contact->makeLink($client->getId(), Yii::$app->user->id);
            }
        }
    }

Заключение

Полная версия с аватаром в расширении sergmoro1/yii2-user.

Оставьте комментарий

Только зарегистрированные пользователи могут оставлять комментарии. Пожалуйста войдите или пройдите регистрацию.