Содержание


Применение сервиса Passport для упрощения управления пользователями и аутентификации в PHP-приложениях для платформы IBM Cloud, Часть 1

Как добавить аутентификацию пользователей и управление ими в PHP-приложение без переписывания его с нуля

Ускорьте процесс разработки, чтобы сконцентрироваться на создании более уникальных аспектов своего приложения

Comments

Серия контента:

Этот контент является частью # из серии # статей: Применение сервиса Passport для упрощения управления пользователями и аутентификации в PHP-приложениях для платформы IBM Cloud, Часть 1

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Применение сервиса Passport для упрощения управления пользователями и аутентификации в PHP-приложениях для платформы IBM Cloud, Часть 1

Следите за выходом новых статей этой серии.

Всякий раз, когда я приступаю к написанию нового веб-приложения, я неизбежно сталкиваюсь со следующими двумя задачами: 1) создание инструментальной панели для добавления, модифицирования и удаления пользователей; 2) реализация рабочего процесса для входа пользователя в систему/выхода пользователя из системы (login/logout). Может показаться, что этих двух задач действительно невозможно избежать при построении любого приложения разумного размера… однако эти задачи широко распространены и стандартизованы до такой степени, что для большинства разработчиков их реализация обычно является вполне рутинным мероприятием (и довольно скучным).

В поисках более совершенного (и менее утомительного) подхода я наткнулся на сервис IBM Bluemix Passport, интеграция которого позволяет разработчикам передать всю функциональность управления пользователями и их аутентификации внешнему API-интерфейсу. Это существенно уменьшает объем программного кода, который разработчику надо написать (и протестировать) при создании нового приложения. Кроме того, имеет место дополнительный бонус в виде быстрой реализации входа пользователя в систему (user login), что позволяет разработчику заняться более интересными вещами.

В этом состоящем из двух частей учебном пособии я поэтапно описываю процесс построения простого веб-приложения и его развертывания на платформе Bluemix. Используемое в качестве примера приложение поддерживает работу с несколькими пользователями, однако я не буду писать никакого кода для управления пользователями, а вместо этого интегрирую сервис Bluemix' Passport и позволю ему выполнять всю тяжелую нагрузку. Продолжайте чтение этой статьи!

Опробовать демонстрационную версиюПолучить код в GitHub

Что нам потребуется

Обычно для управления пользователями внутри приложения нужно создать специальную инструментальную панель с такими функциями как добавление новых пользователей, редактирование существующих пользователей и активация/деактивация/удаление учетных записей пользователей. Как правило, также необходим способ для входа пользователя в приложение/выхода из приложения, а также для проверки того, прошел ли пользователь аутентификацию – прежде чем разрешать ему доступ к определенным функциям. В некоторых приложениях реализованы дополнительные функции, такие как доступ на основе ролей, профили пользователей и дополнительные рабочие процессы для манипулирования с забытыми паролями и повторной активации пользователя.

На заднем плане для реализации всех этих функций обычно требуется (как минимум) создать базу данных пользователей, применить алгоритм безопасности для шифрования и валидации паролей пользователей, а также написать SQL-запросы для создания, обновления, удаления и аутентификации пользователей. Интеграция с сервисом Bluemix Passport избавляет от необходимости создания базы данных пользователей и написания SQL-запросов – все эти задачи теперь выполняются посредством API-вызовов сервиса Passporte.

Поскольку интефейс Passport API является REST-совместимым, к нему можно обращаться с помощью любого языка программирования; в этой статье я буду использовать язык PHP. Кроме того, я использую Bootstrap для создания интерфейса, оптимизированного для мобильных решений, микрофреймворк Slim PHP для управления потоками приложения, клиента Guzzle PHP для обращения к интерфейсу Passport API и Bluemix в качестве инфраструктуры и платформ для хостинга.

Перед началом работы со статьей убедитесь в соблюдении следующих необходимых условий:

Примечание. Любое приложение, использующее сервис Passport, должно соответствовать документу Inversoft License Agreement (Лицензионное соглашение Inversoft). Аналогично, любое приложение, использующее Bluemix, должно соответствовать документу Требования по использованию Bluemix, какs описано по этой ссылке. Прежде чем приступить к своему проекту, уделите несколько минут изучению этих требований и убедитесь в том, что ваше приложение им соответствует.

Шаг 1. Создание скелета приложения

На первом шаге производится инициализация базового приложения с микрофреймворком Slim PHP и HTTP-клиентом Guzzle PHP. Оба этих компонента можно загрузить и установить с помощью Composer – менеджера зависимостей для PHP. Используйте следующий конфигурационный файл Composer, который должен быть сохранен в каталоге $APP_ROOT/composer.json (где "$APP_ROOT" – это каталог вашего проекта):

 { "require": { "slim/slim": "^3.8", "slim/php-view": "^2.2", "guzzlehttp/guzzle": "^6.3" } }

Затем с помощью Composer произведите установку посредством следующей команды:

 shell> php composer.phar install

После того, как необходимые компоненты будут загружены с помощью Composer, создайте следующие два каталога: $APP_ROOT/public для всех файлов, доступных через Интернет, и $APP_ROOT/views для всех представлений.

 shell> cd myapp shell> mkdir public views

Затем создайте файл $APP_ROOT/config.php со следующей информацией (заполнители будут заменены реальными элементами на Шаге 3).

 <?php $config = [ 'settings' => [ 'displayErrorDetails' => true, // disable for production 'passport_api_key' => 'PASSPORT-API-KEY', 'passport_api_url' => 'PASSPORT-API-URL', 'passport_app_id' => 'PASSPORT-APP-ID', ] ];

Чтобы упростить доступ к приложению, в вашей среде разработки можно также определить новый виртуальный хост с именем myapp.localhost и в качестве его корневого каталога документов указать $APP_ROOT/public. Кроме того, вам следует в каталог $APP_ROOT/public добавить файл .htaccess со следующими настройками:

 <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.php [QSA,L] </IfModule>

Задействование виртуального хоста, отображенного на каталог $APP_ROOT/public, позволяет вам обращаться к маршрутам приложения напрямую с использованием имени виртуального хоста — например, http://myapp.localhost/admin/users/index вместо http://localhost/public/admin/users/index. Чтобы узнать больше о виртуальных хостах и о конфигурации веб-сервера для приложений фреймворка Slim, обратитесь к следующим документам: Slim documentation (Документация по Slim), Slim configuration for Apache (Конфигурация Slim для Apache) и Composer documentation (Документация по Composer).

На следующем шаге мы создаем скрипт контроллера, который будет инициализировать фреймворк Slim. Он также будет содержать обратные вызовы к маршрутам приложения, при этом каждый обратный вызов будет определять программный код, подлежащий исполнению в тех случаях, когда маршрут совпадает с входящим запросом. Создайте этот скрипт в файле $APP_ROOT/public/index.php со следующим содержимым:

 <?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; require '../vendor/autoload.php'; require '../config.php'; // сконфигурировать экземпляр Slim-приложения // инициализировать приложение $app = new \Slim\App($config); // инициализировать контейнер инъекции зависимости $container = $app->getContainer(); // добавить рендерер представления $container['view'] = function ($container) { return new \Slim\Views\PhpRenderer("../views/"); }; // обработчик индексной страницы $app->get('/', function (Request $request, Response $response) { return $response->withHeader('Location', $this->router->pathFor('home')); }); // обработчик публичной страницы $app->get('/home', function (Request $request, Response $response) { return $this->view->render($response, 'home.phtml', [ 'router' => $this->router ]); })->setName('home'); $app->run();

Slim работает посредством определения функций обратных вызовов для HTTP-методов и конечных точек. Это осуществляется путем вызова соответствующего Slim-метода — метода get() для запросов GET, метода post() для запросов POST и т.д. — и передачи маршрута, подлежащего сопоставлению, в качестве первого аргумента в метод. Второй аргумент метода – анонимная функция, специфицирующая действия, которые необходимо предпринять, если маршрут соответствует входящему запросу.

Показанный выше скрипт настраивает два обработчика (в ближайшее время мы добавим и другие обработчики). Первый обработчик представляет собой простое перенаправление, которое перенаправляет все запросы для маршрута "/" к маршруту "/home". Второй обработчик – это сам маршрут "/home", который осуществляет рендеринг контента файла $APP_ROOT/views/home.phtml file. Создайте этот файл с содержимым, показанным ниже:

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>My App</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css"> <!-- HTML5 shim и Respond.js для поддержки в IE8 элементов и медиазапросов HTML5 --> <!-- ПРЕДУПРЕЖДЕНИЕ: Respond.js не работает, если вы просматриваете страницу посредством file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <div class="container"> <!-- область заголовка --> <div class="panel panel-default"> <div class="panel-heading clearfix"> <h4 class="pull-left">Home</h4> <div class="btn-group pull-right"> <a role="button" class="btn btn-primary" href="<?php echo $data['router']->pathFor('home'); ?>">Home</a> </div> </div> </div> <!-- конец области заголовка --> <!-- область содержимого --> <p>This is the home page. It is public and accessible to everyone.</p> <!-- конец области содержимого --> <!-- footer --> <div class="container"> </div> <!-- end of footer --> </div> </body> </html>

Этот шаблон содержит простой интерфейс пользователя на основе Bootstrap, содержащий следующие области: заголовок, футер и содержимое. Этот базовый шаблон будет использоваться и для всех последующих страниц, при этом будет изменяться лишь область содержимого.

Чтобы увидеть все это в действии, перейдите к маршруту "/home" (либо http://myapp.localhost/home, либо http://localhost/public/home – в зависимости от того, используете ли вы виртуальный хост или нет). Вы должны увидеть результат рендеринга шаблона, показанного выше.

Рисунок 1. Статическая начальная страница
Static home page
Static home page

Шаг 2. Создание формы для регистрации пользователей

После того, как вы настроили скелет базового приложения, вы можете приступать к построению остальной части этого приложения. В качестве первого шага по реализации функциональности управления пользователями, создайте форму для регистрации новых пользователей приложения с минимальным количеством требуемых полей. Начните с определения маршрута "/admin/users/save" и соответствующей функции обратного вызова, для чего добавьте показанный ниже код в файл $APP_ROOT/public/index.php:

 <?php // инициализация Slim-приложения - фрагмент // обработчик формы пользователя $app->get('/admin/users/save', function (Request $request, Response $response) { $response = $this->view->render($response, 'users-save.phtml', [ 'router' => $this->router ]); return $response; })->setName('admin-users-save'); // другие обратные вызовы

По существу, этот код дает указание Slim отвечать на запросы GET к конечной точке /admin/users/save посредством содержимого именованного шаблона. Этот шаблон, расположенный в файле $APP_ROOT/views/users-save.phtml, должен содержать форму, необходимую для создания нового пользователя. Соответствующий код выглядит следующим образом:

 <div> <form method="post" action="<?php echo $data['router']->pathFor('admin-users-save'); ?>"> <div class="form-group"> <label for="fname">First name</label> <input type="text" class="form-control" id="fname" name="fname"> </div> <div class="form-group"> <label for="lname">Last name</label> <input type="text" class="form-control" id="lname" name="lname"> </div> <div class="form-group"> <label for="email">Email address</label> <input type="text" class="form-control" id="email" name="email"> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" class="form-control" id="password" name="password"> </div> <div class="form-group"> <button type="submit" name="submit" class="btn btn-default">Save</button> </div> </form> </div>

Коротко и понятно! От пользователя требуется лишь следующая информация: имя, фамилия, адрес электронной почты и пароль. Адрес электронной почты также служит уникальным идентификатором при входе в систему.

Если в своем браузере вы обратитесь по маршруту "/admin/users/save", то увидите примерно следующее:

Рисунок 2. Форма для создания учетной записи пользователя
User account creation form
User account creation form

Итак, создание точки входа для добавления новых пользователей завершено. На следующем шаге нужно обработать данные, представленные в вышеуказанной форме. Для этого сначала нужно сконфигурировать сервис Passport и интегрировать его с платформой Bluemix.

Шаг 3. Конфигурирование сервиса Passport

Чтобы приступить к использованию Passport API, вам потребуется три порции информации: API key (ключ API), API URL (URL-адрес API) и application ID (идентификатор приложения). Чтобы сконфигурировать экземпляр сервиса Bluemix, вам также потребуется URL-адрес бэкенда (обычно он совпадает с URL-адресом API) и URL-адрес фронтенда вашего экземпляра Passport. Ниже описано, каким образом можно собрать эту информацию:

  • Когда вы первый раз регистрировали свою учетную запись Passport, вам была показана страница успешной регистрации, содержащая следующую информацию: API URL (URL-адрес API), front-end URL (URL-адрес фронтенда) и back-end URL (URL-адрес бэкенда). Если вы в тот момент не обратили на нее внимания, вы всегда можете взять эту информацию со страницы своей учетной записи на веб-сайте Inversoft. Она выглядит следующим образом:
    Рисунок 3. URL-адреса Passport
    Passport URLs
    Passport URLs
  • В процессе настройки сервиса Passport мастер настройки Passport сообщал вам о необходимости настройки ключа API. Если вы в тот момент не обратили на это внимание, перейдите по URL-адресу фронтенда Passport и войдите в систему со своими полномочиями администратора. После этого извлеките ключ API с помощью меню Settings -> API Keys. Все это выглядит следующим образом:
    Рисунок 4. Ключ API
    Passport API key
    Passport API key
  • Кроме того, в процессе настройки мастер предлагал вам настроить ваше первое приложение и сгенерировал для вас уникальный идентификатор приложения. Если вы в тот момент не обратили на это внимание, перейдите по URL-адресу фронтенда Passport и войдите в систему со своими полномочиями администратора. После этого извлеките идентификатор приложения с помощью меню Settings -> Applications.
    Рисунок 5. Идентификатор приложения
    Passport application ID
    Passport application ID

После того, как у вас будет вся необходимая информация, инициализируйте новый экземпляр сервиса Passport в среде Bluemix. Для этого войдите в систему Bluemix под своей учетной записью и в инструментальной панели нажмите на кнопку Catalog. В появившемся списке сервисов выберите Application Services, а затем Passport. Введите значения API key, front-end URL и back-end URL, а затем нажмите кнопку Create, чтобы создать сервис. Пока оставьте этот сервис несвязанным (вы свяжете его со своим приложением на Шаге 8).

Рисунок 6. Создание сервиса Passport в среде Bluemix
Passport service creation on Bluemix
Passport service creation on Bluemix

В конечном итоге вы хотите протолкнуть свое приложение в Bluemix и подключить его к интерфейсу Passport API, воспользовавшись полномочиями экземпляра сервиса связывания, однако ваше приложение все еще находится в процессе разработки. Итак, пока вы находитесь на стадии разработки, вручную введите в файл $APP_ROOT/config.php значения API key, API endpoint и application ID. В результате этого вы сможете использовать интерфейс Passport API из своей системы разработки, а затем импортировать этот конфигурационный файл в свое Slim-приложение.

Для получения дополнительной информации обратитесь к следующим документам: Passport Bluemix service documentation (Документация по сервису Passport Bluemix), Passport and Bluemix integration tutorial (Руководство по интеграции Passport и Bluemix) и Guzzle documentation (Документация по Guzzle).

Шаг 4. Обработка регистрационных данных пользователя

После того, как вы сконфигурировали значения API key, API endpoint и application ID, вы можете приступить к обработке регистрационных данных пользователя, которые были представлены с помощью соответствующей формы на Шаге 2. Сначала инициализируйте клиента Guzzle HTTP и сконфигурируйте его для работы с интерфейсом Passport API, для чего добавьте показанный ниже код в файл $APP_ROOT/public/index.php (до функций обратного вызова):

 <?php // инициализация Slim-приложения - фрагмент // добавить клиента Passport API $container['passport'] = function ($container) { $config = $container->get('settings'); return new Client([ 'base_uri' => $config['passport_api_url'], 'timeout' => 6000, 'verify' => false, // set to true for production 'headers' => [ 'Authorization' => $config['passport_api_key'], ] ]); }; // другие обратные вызовы

В показанном выше коде контейнер Slim используется для инъекции зависимости, чтобы сконфигурировать клиента Guzzle и подготовить его к применению. Обратите внимание, что клиент сконфигурирован таким образом, чтобы автоматически включать в каждый запрос заголовок Authorization, содержащий API key (из конфигурационного файла, который вы обновили на предыдущем шаге). Это необходимый аутентификационный этап при обращении к Passport API; в отсутствие этого заголовка попытка доступа к API будет отвергнута. Для получения дополнительной информации по аутентификации Passport API обратитесь к следующим документам: Passport API authentication (Аутентификация Passport API) и Passport user registration API documentation (Документация по API-интерфейсам регистрации пользователя Passport).

После того, как форма была представлена, она поступает в процессор формы, который принимает эту форму и осуществляет ее валидацию, а затем создает учетную запись пользователя с помощью Passport API. Ниже показан обязательный код, который нужно добавить в файл $APP_ROOT/public/index.php:

 <?php // инициализация Slim-приложения - фрагмент // процессор формы пользователя $app->post('/admin/users/save', function (Request $request, Response $response) { // получить конфигурацию $config = $this->get('settings'); // получить входные значения $params = $request->getParams(); // осуществить валидацию входной информации if (!($fname = filter_var($params['fname'], FILTER_SANITIZE_STRING))) { throw new Exception('ERROR: First name is not a valid string'); } if (!($lname = filter_var($params['lname'], FILTER_SANITIZE_STRING))) { throw new Exception('ERROR: Last name is not a valid string'); } $password = trim(strip_tags($params['password'])); if (strlen($password) < 8) { throw new Exception('ERROR: Password should be at least 8 characters long'); } $email = filter_var($params['email'], FILTER_SANITIZE_EMAIL); if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { throw new Exception('ERROR: Email address should be in a valid format'); } // генерировать массив данных пользователя $user = [ 'registration' => [ 'applicationId' => $config['passport_app_id'], ], 'skipVerification' => true, 'user' => [ 'email' => $email, 'firstName' => $fname, 'lastName' => $lname, 'password' => $password ] ]; // закодировать данные пользователя в формате JSON // направить запрос POST к Passport API для регистрации и создания пользователя $apiResponse = $this->passport->post('/api/user/registration', [ 'body' => json_encode($user), 'headers' => ['Content-Type' => 'application/json'], ]); // в случае успеха отобразить сообщение об успехе // с идентификатором пользователя ($apiResponse->getStatusCode() == 200) { $json = (string)$apiResponse->getBody(); $body = json_decode($json); $response = $this->view->render($response, 'users-save.phtml', [ 'router' => $this->router, 'user' => $body->user ]); return $response; } }); // другие обратные вызовы

В этом листинге программного кода определен обратный вызов, обрабатывающий представленные формы посредством запроса POST. При этом много чего происходит, поэтому рассмотрим все это поэтапно:

  • Обратный вызов начинается со сбора различных входных параметров — имя, адрес электронной почты, пароль — и валидации каждого из них с помощью различных валидаторов. Некорректная входная информация помечается флагом, после чего бросается исключение, чтобы предотвратить дальнейшую обработку.
  • После того, как входная информация успешно прошла санацию и валидацию, она конвертируется в PHP-массив, содержащий два основных ключа. Ключ registration содержит идентификатор приложения, для которого регистрируется данный пользователь; этот ключ извлекается из конфигурационного файла приложения. Ключ user содержит подробности создаваемой учетной записи пользователя. Затем этот PHP-массив конвертируется в JSON-документ с помощью PHP-функции json_encode().
  • После этого клиент Guzzle отсылает запрос POST в конечную точку /api/user/registration интерфейса Passport API с документом, закодированным в формате JSON. Этот API-вызов создает в сервисе Passport нового пользователя с вышеуказанными значениями адреса электронной почты, пароля и других параметров, а затем связывает этого пользователя с соответствующим приложением.
  • Если все прошло успешно, API-вызов возвращает JSON-документ, содержащий новую запись пользователя вместе с ассоциированным с ней уникальным идентификатором. Этот JSON-документ декодируется и передается обратно в шаблон в качестве переменной шаблона.

После этого производится обновление шаблона в файле $APP_ROOT/views/users-save.phtml, чтобы осуществить проверку на наличие возвращенной записи пользователя, и отображается сообщение об успехе, как показано в этой версии формы регистрации пользователя:

 <div> <?php if (!isset($_POST['submit'])): ?> <form method="post" action="<?php echo $data['router']->pathFor('admin-users-save'); ?>"> <div class="form-group"> <label for="fname">First name</label> <input type="text" class="form-control" id="fname" name="fname"> </div> <div class="form-group"> <label for="lname">Last name</label> <input type="text" class="form-control" id="lname" name="lname"> </div> <div class="form-group"> <label for="email">Email address</label> <input type="text" class="form-control" id="email" name="email"> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" class="form-control" id="password" name="password"> </div> <div class="form-group"> <button type="submit" name="submit" class="btn btn-default">Save</button> </div> </form> <?php else: ?> <div class="alert alert-success"> <strong>Success!</strong> The user with identifier <strong><?php echo $data['user']->id; ?></strong> was successfully created. <a role="button" class="btn btn-primary" href="<?php echo $data['router']->pathFor('admin-users-save'); ?>">Add another?</a> </div> <?php endif; ?> </div>

Чтобы увидеть все это в действии, попытайтесь создать нового пользователя с помощью данного приложения. В случае успеха вы должны увидеть сообщение следующего вида, с:

Рисунок 7. Создание нового пользователя
New user creation
New user creation

Шаг 5. Получение списка пользователей

Интерфейс Passport API содержит API для поиска пользователей (расположен по адресу /api/user/search. Он весьма удобен в тех случаях, когда вы хотите извлечь список пользователей, зарегистрированных для работы с вашим приложением. Чтобы увидеть все это в действии, добавьте в файл $APP_ROOT/public/index.php маршрут и функцию обратного вызова к конечной точке этого API, как показано ниже:

 <?php // инициализация Slim-приложения - фрагмент // обработчик списка пользователей $app->get('/admin/users/index', function (Request $request, Response $response) { // получить конфигурацию $config = $this->get('settings'); $apiResponse = $this->passport->get('/api/user/search', [ 'query' => ['queryString' => 'user.registrations.applicationId:' . $config['passport_app_id']] ]); if ($apiResponse->getStatusCode() == 200) { $json = (string)$apiResponse->getBody(); $body = json_decode($json); $response = $this->view->render($response, 'users-index.phtml', [ 'router' => $this->router, 'results' => $body ]); return $response; } })->setName('admin-users-index'); // другие обратные вызовы

Этот обратный вызов вызывает API-метод /api/user/search и извлекает всех пользователей, ассоциированных с текущим приложением, в виде JSON-документа. Затем этот документ конвертируется в PHP-массив и передается в скрипт просмотра, который отображает данные в виде таблицы. Ниже показана релевантная секция скрипта просмотра, которую следует сохранить в файле $APP_ROOT/views/users-index.phtml:

 <?php if ($data['results']->total > 0): ?> <table class="table table-striped"> <thead> <tr> <th>Name</th> <th>Email address</th> </tr> </thead> <?php foreach ($data['results']->users as $user): ?> <tr> <td><?php echo $user->firstName; ?> <?php echo $user->lastName; ?></td> <td><?php echo $user->email; ?></td> </tr> <?php endforeach; ?> </table> <?php else: ?> <div> <div class="alert alert-info"> No users found. <a role="button" class="btn btn-primary" href="<?php echo $data['router']->pathFor('admin-users-save'); ?>">Why not create one?</a> </div> </div> <?php endif; ?>

Если вы перейдете к маршруту "/admin/users/index", он будет выглядеть следующим образом:

Рисунок 8. Список пользователей
User list
User list

Для получения дополнительной информации обратитесь к документу: Passport user search API documentation (Документация API-интерфейсу для по поиска пользователей Passport).

Шаг 6. Реализация входа в систему/выхода из системы (login/logout)

Итак, теперь у вас есть способ для регистрации пользователей своего приложения. На следующем шаге мы добавим немного аутентификации, благодаря чему вы можете различать зарегистрированных и незарегистрированных пользователей (и, возможно, разрешать доступ к определенной функциональности лишь зарегистрированным пользователям). Короче говоря, пришло время реализовать рабочий процесс входа пользователя в систему (login).

Passport предлагает API-интерфейс входа в систему, который можно применять для аутентификации пользователей на соответствие сохраненным мандатам. Использование этого API-интерфейса достаточно просто: Пошлите запрос POST в конечную точку /api/login с закодированным в формате JSON телом, содержащим идентификатор приложения, адрес электронной почты пользователя и пароль пользователя. В случае успеха этот API возвратит код ответа 2xx, в противном случае он возвратит код ответа 4xx. В обоих случаях возможно несколько кодов ответа – в зависимости от конкретных сценариев. В документе Passport user login API documentation (Документация Passport по API-интерфейсу входа пользователя в систему) приведен полный список кодов ответа с подробностямиs.

Ниже показан обязательный код, который вам нужно добавить в файл $APP_ROOT/public/index.php для использования в качестве обратных вызовов для маршрута "/login":

 <?php // инициализация Slim-приложения - фрагмент session_start(); // обработчик страницы входа в систему $app->get('/login', function (Request $request, Response $response) { return $this->view->render($response, 'login.phtml', [ 'router' => $this->router ]); })->setName('login'); // процессор формы входа в систему $app->post('/login', function (Request $request, Response $response) { // получить конфигурацию $config = $this->get('settings'); // по умолчанию присвоить записи пользователя значение false $user = false; try { // получить входные значения $params = $request->getParams(); // подвергнуть входную информацию валидации и санации $email = filter_var($params['email'], FILTER_SANITIZE_EMAIL); if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) { throw new Exception('ERROR: Email address should be in a valid format'); } $password = trim(strip_tags($params['password'])); if (empty($password)) { throw new Exception('ERROR: Password should not be an empty string'); } // генерировать массив данных для аутентификации $auth = [ 'applicationId' => $config['passport_app_id'], 'loginId' => $email, 'password' => $password, ]; // осуществить аутентификацию $apiResponse = $this->passport->post('/api/login', [ 'body' => json_encode($auth), 'headers' => ['Content-Type' => 'application/json'], ]); // если код ответа 2xx, аутентификация прошла успешно // задать информацию пользователя в сеансе if ($apiResponse->getStatusCode() == 200 || $apiResponse->getStatusCode() == 202) { $json = (string)$apiResponse->getBody(); $body = json_decode($json); $_SESSION['user'] = $body->user; $user = $body->user; } } catch (ClientException $e) { // при наличии исключения Guzzle // если код ответа 4xx, ошибка аутентификации // обойти обработчик исключения и перейти к странице неудачного входа // в случае других ошибок перейти к обработчику исключения обычным образом if (!($e->getResponse()->getStatusCode() >= 400 && $e->getResponse()->getStatusCode() < 500)) { throw new Exception($e->getResponse()); } } return $this->view->render($response, 'login.phtml', [ 'router' => $this->router, 'user' => $user ]); }); // другие обратные вызовы

Первый обработчик получает запросы GET для маршрута "/login" и возвращает форму входа в систему (вскоре вы увидите это).

Второй обработчик получает представленные данные из формы входа в систему и проверяет их, чтобы убедиться в том, что они содержат корректные значения адреса электронной почты и пароля. Если эта сведения корректны, обработчик создает JSON-документ, содержащий идентификатор приложения, адрес электронной почты и пароль, а затем посылает запросы POST в конечную точку e /api/login.

  • Если API-интерфейс отвечает кодом 200 или 202, это означает, что аутентификация оказалась успешной. Кроме того, этот ответ будет содержать полную запись пользователя в формате JSON; эта запись декодируется, после чего вместе с кодом ответа помещается в переменную сеанса с именем $_SESSION['user'], что позволяет использовать эту информацию в последующих операциях.
  • Если API-интерфейс отвечает кодом 4xx, это означает, что аутентификация была неудачной. В нормальных условиях Guzzle автоматически рассматривает отличные от 2xx коды как ошибки и конвертирует их в исключения. В данном конкретном случае, если код ошибки равен 4xx, то обратный вызов прерывает обычный процесс обработки исключения и передает управление шаблону страницы входа в систему. Это позволяет отобразить специальное сообщение об ошибке вместо обычного шаблона исключения.

Чтобы действовать соответственно, вам следует создать шаблон входа в систему, содержащий как исходную форму входа в систему, так и сообщения об успехе и неудаче, которые демонстрируется после попытки входа в систему. Ниже показан код, который следует сохранить в файле $APP_ROOT/views/login.phtml:

 <?php if (!isset($_POST['submit'])): ?> <div> <form method="post" action="<?php echo $data['router']->pathFor('login'); ?>"> <div class="form-group"> <label for="email">Email address</label> <input type="text" class="form-control" id="email" name="email" value=""> </div> <div class="form-group"> <label for="body">Password</label> <input type="password" class="form-control" id="password" name="password"> </div> <div class="form-group"> <button type="submit" name="submit" class="btn btn-default">Submit</button> </div> </form> </div> <?php else: ?> <div> <?php if ($data['user'] !== false): ?> <div class="alert alert-success"> <strong>Success!</strong>. </div> <p>You are currently logged in as <strong><?php echo $data['user']->firstName; ?> <?php echo $data['user']->lastName; ?></strong> with email address <strong><?php echo $data['user']->email; ?></strong>.</p> <p>Visit the <a href="<?php echo $data['router']->pathFor('account'); ?>">account information page.</p> <?php else: ?> <div class="alert alert-danger"> <strong>Failure!</strong> Please <a href="<?php echo $data['router']->pathFor('login'); ?>">try again</a>. </div> <?php endif; ?> </div> <?php endif; ?>

Большая часть этого кода уже была объяснена в предыдущих параграфах. Данный шаблон содержит форму входа в систему, которая имеет поля email address (адрес электронной почты) и password (пароль). После представления этих данных отображается либо сообщение об успехе, либо сообщение о неудаче – в зависимости от значения переменной шаблонаe $_SESSION['auth']. Если вход в систему оказался успешным, то из переменной сеанса можно с легкостью извлечь имя пользователя, адрес электронной почты и другие подробности, а затем отобразить их как часть сообщения об успехе.

Форма входа в систему выглядит следующим образом:

Рисунок 9. Вход пользователя в приложение
User login
User login

Ниже показан пример того, что происходит после успешного входа в систему:

Рисунок 10. Результат входа пользователя в систему
User login result
User login result

Помимо маршрута "/login" вам также требуется маршрут"/logout". Интерфейс Passport API включает в себя метод /logout, однако он полезен лишь тогда, когда вы обновляете токены или обращаетесь к ним. Поскольку рассматриваемое приложение не делает этого, то у него нет необходимости в явном виде "выгружать" пользователя из Passport; вместо этого операция выхода из системы может быть реализована простым уничтожением переменной сеанса для области определения приложения. Соответствующий код выглядит следующим образом:

 <?php // инициализация Slim-приложения - фрагмент // обработчик страницы выхода из системы (logout) $app->get('/logout', function (Request $request, Response $response) { unset($_SESSION['user']); return $response->withHeader('Location', $this->router->pathFor('login')); })->setName('logout'); // другие обратные вызовы

Шаг 7. Реализация аутентификационных проверок

После того, как у вас появился способ для аутентификации пользователей, вы можете достаточно легко защитить определенные страницы и гарантировать, что они будут видны только пользователям, успешно вошедшим в систему. Ниже показана функция, которая делает именно это (добавьте ее в файл $APP_ROOT/public/index.php перед другими обработчиками обратных вызовов):

 <?php // инициализация Slim-приложения - фрагмент // простое ПО связующего уровня для аутентификации $authenticate = function ($request, $response, $next) { if (!isset($_SESSION['user'])) { return $response->withHeader('Location', $this->router->pathFor('login')); } return $next($request, $response); }; // другие обратные вызовы

Функция authenticate() проверяет наличие в сеансе идентификатора пользователя. Если таковой отсутствует, эта функция перенаправляет пользователя по URL-адресу /logout, что принуждает его к повторному входу в систему. Эта функция используется в программном обеспечении Slim "связующего уровня", которое представляет собой код, исполняемый перед обработкой запроса. Добавив это ПО к обработчикам определенных маршрутов, можно защитить доступ к функциям приложения, благодаря чему они будут доступны только аутентифицированным пользователям.

Например, если вы хотите, чтобы страницу с приватной информацией (в данном случае это страница account) могли видеть только успешно вошедшие в систему пользователи, вы можете обычным образом создать для этой страницы обработчик обратного вызова, а затем присоединить "поверх" него вышеупомянутое ПО связующего уровня. Все это будет выглядеть следующим образом:

 <?php // инициализация Slim-приложения - фрагмент // обработчик страницы с приватной информацией $app->get('/account', function (Request $request, Response $response) { return $this->view->render($response, 'account.phtml', [ 'router' => $this->router, 'user' => $_SESSION['user'] ]); })->setName('account')->add($authenticate); // другие обратные вызовы

При наличии этого ПО связующего уровня неаутентифицированные пользователи, пытающиеся обратиться к маршруту "/account", будут перенаправляться на страницу входа в систему (login), а аутентифицированные пользователи будут получить доступ к этому маршруту обычным образом. Для получения дополнительной информации обратитесь к документу Slim middleware documentation (Документация по программному обеспечению Slim связующего уровня).

Шаг 8. Реализация активации и деактивации пользователя

Интерфейс Passport API имеет интересную особенность – это встроенная поддержка активации/деактивации пользователя. Деактивация оказывается полезной в том случае, когда вы хотите временно приостановить действие учетной записи пользователя, но не желаете окончательно удалять ее. Неактивный ("деактивированный") пользователь не сможет войти в систему до тех пор, пока его учетная запись не будет снова активирована ("реактивирована").

Интерфейс Passport API имеет одну и ту же конечную точку API для активации и для деактивации, а для указания на то, какая именно операция подлежит выполнению, используется сам HTTP-метод. Деактивация осуществляется путем посылки запроса DELETE к конечной точке /api/user/USER_ID, а реактивациия осуществляется путем посылки к той же конечной точке запроса PUT с дополнительным параметром reactivate. В обоих случаях API-запрос должен содержать уникальный идентификатор пользователя из Passportt.

Чтобы увидеть все это на практике, добавьте следующие обработчики обратных вызовов в файл $APP_ROOT/public/index.php:

 <?php // инициализация Slim-приложения - фрагмент // обработчик деактивации пользователя $app->get('/admin/users/deactivate/{id}', function (Request $request, Response $response, $args) { // подвергнуть входную информацию санации и валидации if (!($id = filter_var($args['id'], FILTER_SANITIZE_STRING))) { throw new Exception('ERROR: User identifier is not a valid string'); } $apiResponse = $this->passport->delete('/api/user/' . $id); return $response->withHeader('Location', $this->router->pathFor('admin-users-index')); })->setName('admin-users-deactivate'); // обработчик активации пользователя $app->get('/admin/users/activate/{id}', function (Request $request, Response $response, $args) { // sanitize and validate input if (!($id = filter_var($args['id'], FILTER_SANITIZE_STRING))) { throw new Exception('ERROR: User identifier is not a valid string'); } $apiResponse = $this->passport->put('/api/user/' . $id , [ 'query' => ['reactivate' => 'true'] ]); return $response->withHeader('Location', $this->router->pathFor('admin-users-index')); })->setName('admin-users-activate'); // другие обратные вызовы

Каждый из этих обработчиков сначала проверяет, содержит ли запрос маршрута идентификатор пользователя, и если "да", то генерирует запрос DELETE или запрос PUT к интерфейсу Passport API для деактивации или реактивации пользователя.

Как осуществляется переключение между этими маршрутами? Простейший способ – добавить кнопки команд на страницу списка пользователей, созданную на Шаге 5; при этом каждая такая кнопка будет связана с соответствующим маршрутом. Кроме того, имеет смысл разбить список пользователей на категории и отделить активных от неактивных пользователей, так чтобы рядом с каждым пользователем имелась соответствующая кнопка. Модифицированный обработчик страницы списка пользователей выглядит следующим образом:

 <?php // инициализация Slim-приложения - фрагмент $app->get('/admin/users/index', function (Request $request, Response $response) { // получить конфигурацию $config = $this->get('settings'); $apiResponse = $this->passport->get('/api/user/search', [ 'query' => ['queryString' => 'user.registrations.applicationId:' . $config['passport_app_id']] ]); if ($apiResponse->getStatusCode() == 200) { $json = (string)$apiResponse->getBody(); $body = json_decode($json); $activeUsers = []; $inactiveUsers = []; foreach ($body->users as $user) { if ($user->active == 1) { $activeUsers[] = $user; } else { $inactiveUsers[] = $user; } } $response = $this->view->render($response, 'users-index.phtml', [ 'router' => $this->router, 'active-users' => $activeUsers, 'inactive-users' => $inactiveUsers ]); return $response; } })->setName('admin-users-index'); // другие обратные вызовы

Этот обработчик маршрута начинает работу там же, где и прежде: он использует конечную точку /api/user/search для возврата списка всех пользователей, зарегистрированных для данного приложения. Затем он итеративно проходит по возвращенной коллекции, помещая каждого пользователя в один из двух массивов согласно статусу его учетной записи. Затем эти массивы передаются в шаблон страницы, который осуществляет форматирование и отображение списка пользователей с соответствующими кнопками.

Модифицируем шаблон $APP_ROOT/views/users-index.phtml, чтобы включить в него новую информацию и новые кнопки:

 <?php if (count($data['active-users']) > 0): ?> <div class="panel panel-default"> <div class="panel-heading clearfix"> <h4 class="pull-left">Active Users</h4> </div> <table class="table table-striped"> <thead> <tr> <th>Name</th> <th>Email address</th> <th></th> </tr> </thead> <?php foreach ($data['active-users'] as $user): ?> <tr> <td><?php echo $user->firstName; ?> <?php echo $user->lastName; ?></td> <td><?php echo $user->email; ?></td> <td><a href="<?php echo $data['router']->pathFor('admin-users-deactivate', array('id' => $user->id)); ?>" class="btn btn-danger">Deactivate</a></td> </tr> <?php endforeach; ?> </table> </div> <?php else: ?> <div> <div class="alert alert-info"> No users found. <a role="button" class="btn btn-primary" href="<?php echo $data['router']->pathFor('admin-users-save'); ?>">Why not create one?</a> </div> </div> <?php endif; ?> <?php if (count($data['inactive-users']) > 0): ?> <div class="panel panel-default"> <div class="panel-heading clearfix"> <h4 class="pull-left">Inactive Users</h4> </div> <table class="table table-striped"> <thead> <tr> <th>Name</th> <th>Email address</th> <th></th> </tr> </thead> <?php foreach ($data['inactive-users'] as $user): ?> <tr> <td><?php echo $user->firstName; ?> <?php echo $user->lastName; ?></td> <td><?php echo $user->email; ?></td> <td><a href="<?php echo $data['router']->pathFor('admin-users-activate', array('id' => $user->id)); ?>" class="btn btn-success">Activate</a></td> </tr> <?php endforeach; ?> </table> </div> <?php endif; ?>

Результирующий шаблон выглядит следующим образом:

Рисунок 11. Список пользователей
User list
User list

Для получения дополнительной информации обратитесь к документам: Passport user deactivation API documentation (Документация Passport по API-интерфейсу деактивации пользователя) и Passport user reactivation API documentation (Документация Passport по API-интерфейсу реактивации пользователя).

Шаг 9. Развертывание на платформе Bluemi

К этому моменту наше приложение обладает разумным объемом функциональности, поэтому настал момент развернуть его на платформе Bluemix и связать с экземпляром сервиса Passport, который вы создали на Шаге 3. Сначала создайте файл манифеста приложения, напоминающий о необходимости использовании уникальных имен хоста и приложения (эта уникальность обеспечивается посредством прикрепления к этим именам произвольной строки, например, ваших инициалов).

 --- applications: - name: myapp-[initials] memory: 256M instances: 1 host: myapp-[initials] buildpack: https://github.com/cloudfoundry/php-buildpack.git stack: cflinuxfs2

Вы также должны сконфигурировать пакет сборки для использования каталога public приложения в качестве каталога веб-сервера. Создайте файл $APP_ROOT/.bp-config/options.json со следующим содержимым:

 { "WEB_SERVER": "httpd", "PHP_EXTENSIONS": ["bz2", "zlib", "curl"], "COMPOSER_VENDOR_DIR": "vendor", "WEBDIR": "public", "PHP_VERSION": "{PHP_56_LATEST}" }

Обычно разработчик также хочет, чтобы мандаты на пользование сервисом Passport автоматически поступали из Bluemix. Это позволяет ему модифицировать пароли сервиса или отсоединять/присоединять новые экземпляры сервиса без необходимости обновления всего программного кода приложения. Для этого сначала измените программный код таким образом, чтобы использовать Bluemix-переменную VCAP_SERVICES:

 <?php // инициализация Slim-приложения - фрагмент // если BlueMix-переменная среды VCAP_SERVICES доступна // перезаписать локальный конфигурационный файл мандатом от BlueMix if ($services = getenv("VCAP_SERVICES")) { $services_json = json_decode($services, true); $config['settings']['passport_api_key'] = $services_json["user-provided"][0]["credentials"]["api_key"]; $config['settings']['passport_api_url'] = $services_json["user-provided"][0]["credentials"]["passport_backend_url"]; if (getenv("passport_app_id")) { $config['settings']['passport_app_id'] = getenv("passport_app_id"); } } // другие обратные вызовы

Обратите внимание, что показанный выше код получает ключ Passport API и API URL от Bluemix-переменной VCAP_SERVICES, которая представляет собой специальную переменную среды, хранящую подробности соединения для входящих сервисов. Напоминаю, что вы добавили эти значения, когда инициализировали экземпляр сервиса Passport на шаге 3.

Обратите внимание, что этот код пытается получить Passport-идентификатор приложения из специальной переменной в среде Bluemix-приложения. Это хорошая альтернатива хранению идентификатора в конфигурационном файле. Вскоре я покажу, как настраивать эту специальную переменную.

Теперь двигайтесь дальше и протолкните приложение в Bluemix, а затем свяжите с ним сервис Passport, который вы инициализировали ранее. Не забывайте использовать корректный идентификатор для экземпляра сервиса, чтобы гарантировать, что с приложением будет связан надлежащий экземпляр сервиса. Вы можете получить идентификатор сервиса на странице экземпляра сервиса в инструментальной панели Bluemix.

 shell> cf api https://api.ng.bluemix.net shell> cf login shell> cf push shell> cf bind-service myapp-[initials] "Passport-[id]"

Добавьте специальную переменную, в которой будет содержаться Passport-идентификатор приложения:

 shell> cf set-env myapp-[initials] passport_app_id APP_ID

После этого перезапустите приложение, чтобы изменения вступили в силу:

 shell> cf restage myapp-[initials]

Теперь вы должны иметь возможность перейти к приложению по адресу http://myapp-[initials].mybluemix.net и увидеть страницу приветствия. Если вы не в состоянии сделать этого, воспользуйтесь ссылкой в верхней части страницы для получения информации относительно того, как получить журнал отладки. Более подробную информацию можно получить в статье Debugging PHP errors on IBM Cloud (Отладка ошибок PHP в среде IBM Cloud).

Заключение

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

Тем не менее, пока вы увидели лишь вершину айсберга. Интерфейс Passport API предоставляет несколько полезных функциональностей: модифицирование и удаление учетных записей пользователей, хранение специальных атрибутов профиля пользователя, поддержка ролей пользователей, восстановления забытых паролей с помощью готовых к применению рабочих процессов. Все эти функциональности будут рассмотрены во второй части этого цикла, поэтому не пропустите ее!


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Облачные вычисления, Security
ArticleID=1051038
ArticleTitle=Применение сервиса Passport для упрощения управления пользователями и аутентификации в PHP-приложениях для платформы IBM Cloud, Часть 1: Как добавить аутентификацию пользователей и управление ими в PHP-приложение без переписывания его с нуля
publish-date=07242017