Авторизация на сайтах через Z-Payment (OAuth 2.0)

в 7:55, , рубрики: api, oauth, php, z-payment, авторизация, аутентификация, Веб-разработка, Вконтакте, платежная система, метки: , , , , , ,

imageЗдравствуйте читатели! Данная статья описывает протокол API Z-Payment на основе OAuth 2.0, для авторизации пользователей на сторонних сайтах. Признаемся, что регистрироваться всегда лень, не говоря уже о том, что приходится делиться личной информацией (как минимум почтой), а когда это необходимо делать всего лишь один раз, то лень в двойне. Именно по этому Интернет сервисы с большим количеством пользователей, предлагают возможность авторизации на сторонних ресурсах через себя и Z-Payment в этом случае уже не исключение.

Введение

Недавно командой Z-Payment был реализован механизм серверной авторизации на базе протокола OAuth 2.0, предлагающий разработчикам возможность, создавать на сторонних сайтах авторизацию через сервис Z-Payment и запрашивать личные данные авторизованных пользователей с их согласия. Этот метод позволяет реализовать безопасную аутентификацию пользователей на стороннем сайте.

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

  1. Иметь регистрацию на Z-Payment
  2. Зарегистрировать сайт
  3. Сайт должен получить статус публичного, т.е. опубликован в Z-Каталоге

Основной задачей Z-Payment при реализации этого метода авторизации — предоставить разработчикам еще одно средство для создания независимых проектов, пользующихся ресурсами Z-Payment. Успешным примером может служить проект Биржа обмена Z-Change
Процесс авторизация состоит из 4-х шагов:

  1. Переход пользователя для авторизации на Z-Payment
  2. Разрешение пользователем доступа к персональным данным. Пользователь должен дать согласие на предоставление запрашиваемой 3-ей стороной(вас) данных. В случае если пользователь не авторизован на Z-Payment, то ему будет предложено выполнить авторизацию в личный кабинет. После успешной авторизации пользователю будет предложено разрешить доступ к персональным данным
  3. Передача сайту значения code для получения ключа доступа
  4. Получение сервером проекта ключа доступа access_token для доступа к API Z-Payment

Теперь по порядку и подробнее

Дано: сайт. Задача авторизовать/зарегистрировать своих пользователей через Z-Payment.

Шаг 1

Необходимо перенаправить пользователя для авторизации на Z-Payment, сделать это можно GET или POST запросом на адрес https://z-payment.ru/enter.php с параметрами:
client_id — обязательный параметр, четырёхзначный, уникальный идентификатор магазина в Z-Payment, выдается при регистрации сайта в системе.
redirect — обязательный параметр, адрес на который будет переадресован пользователь после прохождения авторизации (домен указанного адреса должен соответствовать основному домену в настройках магазина)
display — обязательный параметр, указывает тип отображения страницы авторизации. На данный момент поддерживает только page, что значит — авторизация в отдельном окне
scope — не обязательный параметр, этот параметр отвечает за список запрашиваемых личных данных о пользователе. Запрашиваемые данные должны быть разделены запятой или пробелом.
вот список возможных запрашиваемых данных:

0 f_name Имя
1 s_name Фамилия
2 m_name Отчество
3 birth_day Дата рождения
4 group Тип аттестата
5 sex Пол
6 e_mail Почта
7 phone Номер мобильного телефона
8 country Страна
9 city Город
10 balance Баланс кошелька

response_type — не обязательный параметр, отвечает за тип ответа, который хотим получить, может принимать значения: code — если хотим делать запросы со стороннего сервера (по умолчанию) или token — если хотим делать запросы с клиента.
Согласно сказанному выше, GET запрос должен выглядеть так:


GET: https://z-payment.ru/enter.php?client_id=ID_ВАШЕГО_МАГАЗИНА&redirect=http://МОЙСАЙТ.ru/login&dispaly=page&scope=f_name,s_name,m_name,phone,city,e_mail&response_type=code

Шаг 2

Перейдя по такой ссылке, пользователь окажется на странице, где ему будет предложено дать согласие для предоставления 3-му лицу (вашему сайту) данные, которые вы запрашиваете в параметре scope, или сначала авторизоваться если он еще этого не делал, а потом дать согласие. Далее с согласия пользователя, в автоматическом режиме он будет переведен на страницу, указанную в параметре redirect.

Шаг 3

Возвращаясь обратно, GET методом будет передан параметр code, который содержит временный код. С помощью него появляется возможность запросить данные, на которые пользователь дал согласие. Время жизни параметра 15 минут, за этот период нужно получить ключ доступа к API access_token с запросившего сайта. Выглядеть будет так:


GET: http://REDIRECT_URI?code=a2a56603b8f57a6fa4dff77380df05206c883f011c40b72630bb5ed6f6479e52a8e
В случае возникновения ошибки строка запроса будет выглядеть так:
GET: http://REDIRECT_URI?error=invalid_request&error_description=Invalid+display+parameter

Шаг 4

На завершающем шаге мы получаем доступ направляя запрос по адресу z-payment.ru/api/get_access_token.php, запрос так же как на шаге 1, может передаваться GET или POST методом и должен содержать следующие параметры:
client_id — обязательный параметр, четырёхзначный, уникальный идентификатор магазина в системе z-payment, выдается при регистрации магазина в мерчанте Z-Payment (см. выше)

code — обязательный параметр, временный код полученный после прохождения авторизации на шаге 3 (см. выше)

format_answer — не обязательный, формат ответа, по умолчанию используется json. Может принимать значения: json, get.
client_secret – обязательный параметр, является электронной подписью. Формируется как hash md5 из склеенной строки параметров запроса и пароля магазина Merchant Key.
Данные участвующие в подписи: client_id + code + MerchantKey — от этого и берём MD5

Результатом запроса, должен быть ответ состоящий из параметров:
access_token, время жизни ключа expires_in (в секундах), user_id номер ZP кошелька user_verification — информация об аттестации пользователя (принимает значение yes в случае если пользователь имеет аттестат), а так же набор запрашиваемых полей из первого запроса.

На этом теоретическое описание процесса авторизации закончено, теперь
на основании всего вышесказанного можно сделать программную реализацию протокола, делать буду на php 5.3 и предполагается, что реализуемые класс должен использоваться в какой-то MVC архитектуре.
Итак, задача — сделать простой класс для автоматизации авторизации через Z-Payment. Хочу сразу оговориться по коду:

  1. define константы нужно выносить в конфигурационный файл, я их использую в примере для наглядности,
  2. Не привожу пример класса реализующего интерфейс IUserStorage, данные можно хранить там где захочется – сессия(SessionUserStorage), база данных(DbUserStorage) или файл(FileUserStorage), а посему достаточно знать только интерфейс. Код документирован, поэтому считаю дополнительное комментирование занудством.

Собственно PHP код

/** @desc ID магазина для которого реализуем подключение */
define("SHOP_ID", "0001");
/** @desc MERCHANT KEY указывается пользователем в настройках магазина */
define("MERCHANT_KEY", "password");

namespace Example;

/** @desc интерфейс для класса выступающего в роли хранилища данных о пользователе */ 
interface IUserStorage
{
	/** @desc загружаем данные из хранилища */
    public function load();
	/** @desc созхраняем данные о пользователе в хранилище */
    public function save(array $data);
	/** @desc очищаем данные о пользователе */
    public function erase();
}

/** @desc собственно класс реализующий логику */
class UserManager
{
    const TOKEN_URL = "https://z-payment.ru/api/get_access_token.php";
    /**
     * @var ExampleIUserStorage
     * @desc хранилище
     */
    protected $storage;
    /**
     * @var $userInfo array
     * @desc Загружаемая информация о пользователе
     */
    protected $userInfo;
    /**
     * @var ExampleControllerBase
	 * @desc предполагаю использование класса в mvc конструкции, поэтому храним ссылку на контроллер
     */
    protected $controller;
    /**
     * @param IUserStorage $storage
     * @desc ограничиваем параметр $storage с низу интерфейсом ExampleIUserStorage
     */
    public function __construct(ExampleIUserStorage $storage)
    {
        $this->storage = $storage;
        $this->userInfo = $this->storage->load();
    }

	/** @desc получааем либо поле, либо все поля целиком */
    public function getUserInfo($field = null)
    {
        if (null === $field)
            return $this->userInfo;

        if (isset($this->userInfo[$field]))
            return $this->userInfo[$field];

        return null;
    }

    /**
     * @return bool
     * @desc проверяем авторизован ли пользователь
     */
    public function isLogin()
    {
        return ($this->userInfo != null);
    }

    /**
     * @desc текущий ZP аккаунт
     * @return string | null
     */
    public function getAccount()
    {
        if (!$this->isLogin())
            return null;
        return $this->userInfo['user_id'];
    }

    /**
     * @return bool
     * @desc проверяем есть ли у пользователя аттестат
     */
    public function isCertified()
    {
        return ($this->getUserInfo('user_verification') == 'yes');
    }

    /**
     * @return bool
     * @desc метод реализующий авторизацию
     */
    public function Login()
    {
		/** @desc получаем переданный параметр code, если mvc конструкция не используется, то можно это сделать так $code = $_REQUEST['code']; */
        $code = $this->controller->getRequest()->get("code");
        /**
         * @desc Контрольная подпись, формируется по алгоритму md5 из строки параметров запроса
         * 1+2+Merchant Key, где Merchant Key Секретный ключ магазина
         */
        $client_secret = md5(SHOP_ID . $code . MERCHANT_KEY);
		/** @desc формируем данные для запроса */
        $params = array(
			/** id магазина */
            'client_id=' . urlencode(SHOP_ID),
			/** переданный код из первого запроса */
            'code=' . urlencode($code),
			/** тип передаваемых данных */
            'format_answer=' . 'json',
			/** контрольная подпись */
            'client_secret=' . urlencode($client_secret)
        );

        $curl = curl_init(self::TOKEN_URL);
		curl_setopt($curl, CURLOPT_HEADER, 1);
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($curl, CURLOPT_POSTFIELDS, implode("&", $params));
		/** @desc физически запрашиваем данные */
		$result = curl_exec($curl);
		curl_close($curl);
		/** @desc конвертируем в массив */
        $result = json_decode($result);
		
		/** @desc проверяем на наличие ошибки */
        if ($result->error != null)
            return false;

		/** @desc сохраняем в хранилище */
        $this->storage->save((array)$result);
        return true;
    }

    /**
     * @desc выход пользователя
     * @return void
     */
    public function LogOut()
    {
		/** @desc очищаем хранилище */
        $this->storage->erase();
    }


    /**
     * @param ExampleControllerBase $controller
     * @return void
     * @desc настраиваем окружение приложения.
     */
    public function setController(ExampleControllerBase $controller)
    {
	    $this->controller = $controller;
        /**
         * @var isLogin bool
         * @desc устанавливаем флаг для отображения
         */
        $controller->getView()->isLogin = $this->isLogin();
        /** @var $client_id int */
        $client_id = SHOP_ID;
        /**
         * @var $redirect_uri string
         * @desc URL возврата
         */
        $redirect_uri =  "http://example_site_domain.org/login";
        $display = "page";
        /**
         * @var $scope strung
         * @desc запрашиваемые даные
         */
        $scope = "f_name,s_name,m_name,sex,birth_day,e_mail,phone,country,city,balance";
        if ($this->isLogin()) {
            /** @desc если пользователь авторизован, создаём для отобржения имя фамилия */
            $controller->getView()->userName = $this->userInfo['f_name'] . " " . 
                    $this->userInfo['s_name'];
        } else {
            /** @desc если пользователь не авторизован, создаём для отображения ссылку для регистрации */
            $controller->getView()->linkZpAuth = sprintf("https://z-payment.eu/enter.php?client_id=%s&redirect_uri=%s&display=%s&scope=%s",
                $client_id, $redirect_uri, $display, $scope);
        }
    }
}

Как видно, процесс авторизации практически идентичен прочими интернет сервисами предлагающими эту возможность, особенно если посмотреть на API ВКонтакте. :)

Благодарю за терпение в процессе чтения моего скромного труда и хочу ответить на вопрос, который вас начал мучить после прочтения первого абзаца — «А зачем мне прикручивать авторизацию через ZP, если пользователей у них значительно меньше чем ВК, Gmail, Facebook и др.?», отвечаю: В отличии от многочисленных реализаций протокола OAuth на других сайтах авторизация через API Z-Payment подразумевает получение верифицированной информации о пользователе, а это весьма важно для всех проектов связанных с электронной коммерцией.

Подробнее об API Z-Payment можно почить в документации InterfaceHTTPS.zip doc файл

Автор: zpayment

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js