Статьи

Пользовательская аутентификация OAuth 2 в Apigility

У меня есть клиент, который пишет   API-интерфейс Apigility, который должен взаимодействовать с базой данных, которая уже существует. Сюда также входит таблица пользователей, которая должна использоваться с аутентификацией Apigility OAuth2.

Получить интеграцию OAuth 2 от Apigility для общения с конкретным именем таблицы довольно просто. Просто добавьте этот конфиг:

'storage_settings' => array(
    'user_table' => 'user',
),

Соответствующему адаптеру в конфигурации zf-mvc-auth => аутентификации.

Однако, если вы хотите использовать разные имена столбцов, это немного сложнее, так как они жестко закодированы в классе OAuth2 \ Storage \ Pdo. Чтобы компоненты Apigility OAuth2 могли просматривать правильные столбцы, вы создаете свой собственный адаптер OAuth2. Я решил расширить ZF \ OAuth2 \ Adapter \ PdoAdapter, который расширяет OAuth2 \ Storage \ Pdo, и перейти оттуда.

ZF \ OAuth2 \ Adapter \ PdoAdapter расширяет базовый класс для добавления хэширования bcrypt. Это хорошо, так что это хорошее место для начала. Я создал новый модуль MyAuth для хранения моего адаптера и его фабрики. Адаптер выглядит так:

<?php
namespace MyAuth;

use ZF\OAuth2\Adapter\PdoAdapter;

/**
 * Custom extension of PdoAdapter to validate against the WEB_User table.
 */
class OAuth2Adapter extends PdoAdapter
{
    public function __construct($connection, $config = array())
    {
        $config = [
            'user_table' => 'legacy_user'
        ];

        return parent::__construct($connection, $config);
    }

    public function getUser($username)
    {
        $sql = sprintf(
            'SELECT * from %s where email_address=:username',
            $this->config['user_table']
        );
        $stmt = $this->db->prepare($sql);
        $stmt->execute(array('username' => $username));

        if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            return false;
        }

        // the default behavior is to use "username" as the user_id
        return array_merge(array(
            'user_id' => $username
        ), $userInfo);
    }

    public function setUser($username, $password, 
        $firstName = null, $lastName = null)
    {
        // do not store in plaintext, use bcrypt
        $this->createBcryptHash($password);

        // if it exists, update it.
        if ($this->getUser($username)) {
            $sql = sprintf(
                'UPDATE %s SET pwd=:password, firstname=:firstName,
                    surname=:lastName WHERE username=:username',
                $this->config['user_table']
            );
            $stmt = $this->db->prepare($sql);
        } else {
            $sql = sprintf(
                'INSERT INTO %s (email_address, pwd, firstname, surname)
                    VALUES (:username, :password, :firstName, :lastName)',
                $this->config['user_table']
            );
            $stmt = $this->db->prepare($sql);
        }

        return $stmt->execute(compact('username', 'password', 'firstName',
            'lastName'));
    }

    protected function checkPassword($user, $password)
    {
        return $this->verifyHash($password, $user['pwd']);
    }
}

Этот код для getUser и setUser () взят непосредственно из OAuth2 \ Storage \ Pdo, и все, что я сделал, это изменил имена столбцов. В этом случае у меня есть email_address для моего имени пользователя и pwd для столбца пароля. Аналогично, я написал свой собственный checkPassword на основе ZF \ OAuth2 \ Adapter \ PdoAdapter, снова изменив ключ массива для проверки на «pwd».

Теперь, когда у нас есть фактическая работа, нам нужно подключить ее к Apigility.

Во-первых, нам нужна фабрика, чтобы DIC мог создать экземпляр нашего адаптера:

<?php
namespace MyAuth;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Db\Adapter\Driver\Pdo\Pdo as PdoDriver;

class OAuth2AdapterFactory implements FactoryInterface
{
    /**
     * Create service
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @return OAuth2Adapter
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $connection = $serviceLocator->get('DB\Master');
        if (!$connection->getDriver() instanceof PdoDriver) {
            throw new \RuntimeException("Need a PDO connection!");
        }

        $pdo = $connection->getDriver()->getConnection()->getResource();
        return new OAuth2Adapter($pdo);
    }  
}

Это довольно стандартный код. Обратите внимание, что DB \ Master — это имя соединения с базой данных, которое установлено в администраторе Apigility. Я немного ленив и полагаю, что это адаптер на основе PDO. Если это не так, он взорвется, поэтому, если вы не используете PDO, он не будет работать как есть!

Чтобы зарегистрировать новый адаптер аутентификации в Apigility, создайте файл конфигурации в config / autoload и вызовите его myauth.global.php или что-то в этом роде:

<?php
return [
    'zf-mvc-auth' => [
        'authentication' => [
            'adapters' => [
                'MyAuth' => [
                    'adapter' => 'ZF\\MvcAuth\\Authentication\\OAuth2Adapter',
                    'storage' => [
                        'storage' => 'MyAuth\OAuth2Adapter',
                        'route' => '/oauth',
                    ],
                ],
            ],
        ],
    ],
];

Адаптер называется MyAuth и теперь доступен для выбора на страницах конфигурации API администратора:

Myauth apigility

Подводить итоги

В общем, действительно легко написать собственную аутентификацию OAuth 2 для Apigility, поскольку это очень гибкая платформа. Я просто изменил имена столбцов здесь, но было бы достаточно просто написать адаптер для другой системы хранения, хотя вам придется переопределить больше методов и, возможно, начать с более подходящего базового класса.