Здравствуйте хабрасообщество! Данная статья описывает протокол API Z-Payment на основе OAuth 2.0, для авторизации пользователей на сторонних сайтах. Признаемся, что регистрироваться всегда лень, не говоря уже о том, что приходится делиться личной информацией (как минимум почтой), а когда это необходимо делать всего лишь один раз, то лень в двойне. Именно по этому Интернет сервисы с большим количеством пользователей, предлагают возможность авторизации на сторонних ресурсах через себя и Z-Payment в этом случае уже не исключение.Введение
Недавно командой Z-Payment был реализован механизм серверной авторизации на базе протокола OAuth 2.0, предлагающий разработчикам возможность, создавать на сторонних сайтах авторизацию через сервис Z-Payment и запрашивать личные данные авторизованных пользователей с их согласия. Этот метод позволяет реализовать безопасную аутентификацию пользователей на стороннем сайте.
Для того, что бы воспользоваться доступом к этому интерфейсу, нужно выполнить 3 обязательных условия:
- Иметь регистрацию на Z-Payment
- Зарегистрировать сайт
- Сайт должен получить статус публичного, т.е. опубликован в Z-Каталоге
Основной задачей Z-Payment при реализации этого метода авторизации — предоставить разработчикам еще одно средство для создания независимых проектов, пользующихся ресурсами Z-Payment. Успешным примером может служить проект Биржа обмена Z-Change
Процесс авторизация состоит из 4-х шагов:
- Переход пользователя для авторизации на Z-Payment
- Разрешение пользователем доступа к персональным данным. Пользователь должен дать согласие на предоставление запрашиваемой 3-ей стороной(вас) данных. В случае если пользователь не авторизован на Z-Payment, то ему будет предложено выполнить авторизацию в личный кабинет. После успешной авторизации пользователю будет предложено разрешить доступ к персональным данным
- Передача сайту значения code для получения ключа доступа
- Получение сервером проекта ключа доступа access_token для доступа к API Z-Payment
Теперь по порядку и подробнее
Дано: сайт. Задача авторизовать/зарегистрировать своих пользователей через Z-Payment.
Шаг 1
Необходимо перенаправить пользователя для авторизации на Z-Payment, сделать это можно GET или POST запросом на адрес
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: 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: REDIRECT_URI?code=a2a56603b8f57a6fa4dff77380df05206c883f011c40b72630bb5ed6f6479e52a8e
В случае возникновения ошибки строка запроса будет выглядеть так:
GET: 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. Хочу сразу оговориться по коду:
- define константы нужно выносить в конфигурационный файл, я их использую в примере для наглядности,
- Не привожу пример класса реализующего интерфейс 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 \Example\IUserStorage * @desc хранилище */ protected $storage; /** * @var $userInfo array * @desc Загружаемая информация о пользователе */ protected $userInfo; /** * @var \Example\Controller\Base * @desc предполагаю использование класса в mvc конструкции, поэтому храним ссылку на контроллер */ protected $controller; /** * @param IUserStorage $storage * @desc ограничиваем параметр $storage с низу интерфейсом \Example\IUserStorage */ public function __construct(\Example\IUserStorage $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 \Example\Controller\Base $controller * @return void * @desc настраиваем окружение приложения. */ public function setController(\Example\Controller\Base $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,group,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.ru/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 файл
