Авторизация на сайте через API социальных сетей с интеграцией в Spring Security / Хабр

Абстрактное описание протокола

Теперь, когда у нас есть представление о ролях, используемых в OAuth, рассмотрим диаграмму их взаимодействия друг с другом.

Рассмотрим описание последовательности шагов на этой диаграмме:

  1. Приложение запрашивает у пользователя авторизацию на доступ к серверу ресурсов.
  2. Если пользователь авторизует запрос, приложение получает разрешение на авторизацию (authorization grant).
  3. Приложение запрашивает авторизационный токен у сервера авторизации (API) путём предоставления информации о самом себе и разрешении на авторизацию от пользователя.
  4. Если подлинность приложения подтверждена и разрешение на авторизацию действительно, сервер авторизации (API) создаёт токен доступа для приложения. Процесс авторизации завершён.
  5. Приложение запрашивает ресурс у сервера ресурсов (API), предоставляя при этом токен доступа для аутентификации.
  6. Если токен действителен, сервер ресурсов (API) предоставляет запрашиваемый ресурс приложению.

Фактический порядок шагов описанного процесса может отличаться в зависимости от используемого типа разрешения на авторизацию, но в целом процесс будет выглядеть описанным образом. Далее мы рассмотрим различные типы разрешений на авторизацию.

Модель.

Я пошел по несколько рискованному и противоречивому пути, объединив в объекте пользователя и

POJO

, и

Контроллер.

В принципе, в терминологии

authenticationauthorization

особой разницы нет – авторизация она и есть авторизация, причем, разные сетевые провайдеры склоняют эти термины на свой лад. Однако я в своем отчете четко различаю 2 понятия – регистрация и непосредственно авторизация или вход (обе основаны на авторизации у социально-сетевого провайдера).

Скажем, при участии в форуме, или подаче комментария надо просто авторизоваться – будь то в первый вход, или в сотый. У себя разделением на регистрацию и простую авторизацию я преследую необходимостью создания пользовательской модели при подаче заявки на регистрацию.

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

usability

); б) как ни обидно, но API социальных сетей не единодушны в предоставлении информации о своих клиентах – скажем,

Технологии

Для решения используем фреймворк Spring Boot и Spring Web, для него требуется:

  1. Java 8 ;

  2. Apache Maven

Авторизация и валидация будет выполнена силами Spring Security и JsonWebToken (JWT).Для уменьшения кода использую Lombok.

Создание приложения

Переходим к практике. Создаем Spring Boot приложение и реализуем простое REST API для получения данных пользователя и списка пользователей.

1 Создание Web-проекта

Создаем Maven-проект SpringBootSecurityRest. При инициализации, если вы это делаете через Intellij IDEA, добавьте Spring Boot DevTools, Lombok и Spring Web, иначе добавьте зависимости отдельно в pom-файле.

2 Конфигурация pom-xml

После развертывания проекта pom-файл должен выглядеть следующим образом:

  1. Должен быть указан parent-сегмент с подключенным spring-boot-starter-parent;

  2. И установлены зависимости spring-boot-starter-web, spring-boot-devtools и Lombok.

3 Создание ресурса REST

Разделим все классы на слои, создадим в папке com.springbootsecurityrest четыре новые папки:

1 Подключаем зависимости

Добавляем новые зависимости в pom-файл.

2 Генерация и хранения токена

Начнем с генерации и хранения токена, для этого создадим папку security и в ней создаем класс JwtTokenRepository с имплементацией интерфейса CsrfTokenRepository (из пакета org.springframework.security.web.csrf).

Интерфейс указывает на необходимость реализовать три метода:

  1. Генерация токена в методе generateToken;

  2. Сохранения токена – saveToken;

  3. Получение токена – loadToken.

Генерируем токен силами Jwt, пример реализации метода.

3 Создание нового фильтра для SpringSecurity

Создаем новый класс JwtCsrfFilter, который является реализацией абстрактного класса OncePerRequestFilter (пакет org.springframework.web.filter). Класс будет выполнять валидацию токена и инициировать создание нового. Если обрабатываемый запрос относится к авторизации (путь /auth/login), то логика не выполняется и запрос отправляется далее для выполнения базовой авторизации.

6 Обработка ошибок

Что бы видеть ошибки авторизации или валидации токена, необходимо подготовить обработчик ошибок. Для этого создаем новый класс GlobalExceptionHandler в корне com.springbootsecurityrest, который является расширением класса ResponseEntityExceptionHandler с реализацией метода handleAuthenticationException.

Метод будет устанавливать статус ответа 401 (UNAUTHORIZED) и возвращать сообщение в формате ErrorInfo.

7 Настройка конфигурационного файла Spring Security.

Все данные подготовили и теперь необходимо настроить конфигурационный файл. В папке com.springbootsecurityrest создаем файл SpringSecurityConfig, который является реализацией абстрактного класса WebSecurityConfigurerAdapter пакета org.springframework.security.config.annotation.web.configuration. Помечаем класс двумя аннотациями: Configuration и EnableWebSecurity.

Customauthenticationprovider.java

(класс совершенно бестолковый, но Spring Security необходимо скормить наследника интерфейса

AuthenticationProvider

, но ближайший по смыслу

PreAuthenticatedAuthenticationProvider

не подходит):

Web authentication api – интерфейсы веб api | mdn

API Web Authentication (также называемое WebAuthn) использует ассиметричную криптографию (систему с открытым ключом) вместо паролей или SMS-сообщений для регистрации, входа и двухфакторной аутентификации на веб-сайтах. Это устраняет многие значительные проблемы безопасности, такие как фишинг, утечки данных и атаки на SMS или иные методы двухфакторной аутентификации, при этом сильно упрощая использование, т.к. пользователям не нужно запоминать десятки сложных паролей.

На многих сайтах уже есть страницы для регистрации и входа в существующие учётные записи, и Web Authentication API может быть как заменой, так и дополнением для них. Как и остальные виды Credential Management API, Web Authentication API содержит два базовых метода: для регистрации и для входа:

Обратите внимание, и create(), и get() работают только в Secure Context (например, когда подключение к серверу происходит через https или сервер работает на localhost).

In their most basic forms, both create() and get() receive a very large random number called a challenge from the server and they return the challenge signed by the private key back to the server. This proves to the server that a user is in possession of the private key required for authentication without revealing any secrets over the network.

In order to understand how the create() and get() methods fit into the bigger picture, it is important to understand that they sit between two components that are outside the browser:

  1. Server – the Web Authentication API is intended to register new credentials on a server (also referred to as a service or a relying party) and later use those same credentials on that same server to authenticate a user.
  2. Authenticator – the credentials are created and stored in a device called an authenticator. This is a new concept in authentication: when authenticating using passwords, the password is stored in a user’s brain and no other device is needed; when authenticating using web authentication, the password is replaced with a key pair that is stored in an authenticator. The authenticator may be embedded into an operating system, such as Windows Hello, or may be a physical token, such as a USB or Bluetooth Security Key.

Авторизация на сайте


Простенький спринговский

application-context-security.xml

Владелец ресурса: пользователь

Владельцем ресурса является пользователь, который авторизует приложение для доступа к своему аккаунту. Доступ приложения к пользовательскому аккаунту ограничен “областью видимости” (scope) предоставленных прав авторизации (например, доступ на чтение или запись).

Идентификатор клиента и секрет клиента

После регистрации приложения сервис создаст учётные данные клиента – идентификатор клиента (client ID) и секрет клиента (client secret). Идентификатор клиента представляет собой публично доступную строку, которая используется API сервиса для идентификации приложения, а также используется для создания авторизационных URL для пользователей.

Испытания


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

Клиент: приложение

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

Клиентская авторизация

С клиентской авторизацией действительно нет ничего сложного.

Согласно

, достаточно асинхронно загрузить

и, «повесить»

VK.init({appid: YOUR_APP_ID})

на событие

window.vkAsyncInit

, после чего уже можно вызвать функцию

VK.Auth.login(authInfo, YOUR_APP_PERMISSIONS)

, которая и выполнит авторизацию пользователя.

Авторизация и запрос через Open API

var vk = {
 data: {},
 api: "//vk.com/js/api/openapi.js",
 appID: YOUR_APP_ID,
 appPermissions: YOUR_APP_PERMISSIONS,
 init: function(){
  $.js(vk.api);
  window.vkAsyncInit = function(){
   VK.init({apiId: vk.appID});
   load();
  }

  function load(){
   VK.Auth.login(authInfo, vk.appPermissions);

   function authInfo(response){
    if(response.session){ // Авторизация успешна
     vk.data.user = response.session.user;
     vk.getFriends();
    }else alert("Авторизоваться не удалось!");
   }
  }
 },
 getFriends: function(){
  VK.Api.call('friends.get', {fields: ['uid', 'first_name', 'last_name'], order: 'name'}, function(r){
   if(r.response){
    r = r.response;
    var ol = $('#clientApi').add('ol');
    for(var i = 0; i < r.length;   i){
     var li = ol.add('li').html(r[i].first_name ' ' r[i].last_name ' (' r[i].uid ')')
    }
   }else alert("Не удалось получить список ваших друзей");
  })
 }
}

$.ready(vk.init);

Во-первых, сразу оговорюсь, что используемые здесь функции

$

— это не

jQuery

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

jQuery

.

Итак,
$.ready(function) — Это обработчик для DOMContentLoaded;
$.js(text) — асинхронно загружает javascript файл;
$(element) — возвращает обёртку над DOM узлом element;
$(element).add(node) — создаёт новый дочерний для element узел node и возвращает обёртку над ним;
$(element).html(string) — обёртка для element.innerHTML = string.


По-сути, этот код делает следующее:

По готовности документа загружается API ВКонтакте, и, после его инициализации, вызывается метод

VK.Auth.login()

, который показывает всплывающее окно,

которое успешно «блочится» любой баннерорезкой

, в котором пользователь должен подтвердить своё согласие предоставить данные клиентскому приложению.


Функция

authInfo

вызывается после согласия или несогласия пользователя, и, в случае успешной авторизации, запрашивает список друзей.

Клиентскую часть таким образом можно считать законченной, однако, стоит как-то предупреждать пользователей о возможности блокировки их браузером окна подтверждения.

Маршруты api, файл budgetmanagerapi/app/routes/auth.js


Займёмся созданием маршрутов API. Для этого перейдём в папку

services/BudgetManagerAPI/app

и создадим в ней директорию

routes

, в которой создадим файл

auth.js

со следующим содержимым:

Настройка package.json


Перейдём в корневую директорию проекта, откроем

package.json

и добавим в него, сразу перед блоком

dependencies

, следующее:

Настройка сервера, файл services/index.js

После того, как мы справились с некоторыми из вспомогательных подсистем, займёмся настройкой сервера. Перейдите в папку

services

и откройте уже имеющийся в ней файл

index.js

. Добавьте в него следующее:

Обновление токена доступа

После истечения срока действия токена доступа все запросы к API с его использованием будут возвращать ошибку “Invalid Token Error”. Если при создании токена доступа был создан и токен для обновления токена доступа (refresh token), последний может быть использован для получения нового токена доступа от авторизационного сервера.

Ниже представлен пример POST-запроса, использующего токен для обновления токена доступа для получения нового токена доступа:

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

После того, как приложение получит токен доступа, оно может использовать этот токен для доступа к пользовательскому аккаунту через API сервиса с заданными ограничениями доступа до тех пор, пока не истечёт срок действия токена или токен не будет отозван.

Ниже представлен пример запроса к API с использованием curl. Обратите внимание, что он содержит токен доступа:

Процесс с учётными данными владельца ресурса

После того, как пользователь передаст свои учётные данные приложению, приложение запросит токен доступа у авторизационного сервера. Пример POST-запроса может выглядеть следующим образом:

Процесс с учётными данными клиента

Приложение запрашивает токен доступа путём отправки своих учётных данных, своего идентификатора клиента и секрета клиента авторизационному серверу. Пример POST-запроса может выглядеть следующим образом:

Разрешение на авторизацию

В абстрактном описании протокола выше первые четыре шага касаются вопросов создания разрешения на авторизацию и токена доступа. Тип разрешения на авторизацию зависит от используемого приложением метода запроса авторизации, а также от того, какие типы разрешения поддерживаются со стороны API. OAuth 2 определяет четыре разных типа, каждый из которых полезен в определённых ситуациях:

  • Код авторизации (Authorization Code): используется с серверными приложениями (server-side applications).
  • Неявный (Implicit): используется мобильными или веб-приложениями (приложения, работающие на устройстве пользователя).
  • Учётные данные владельца ресурса (Resource Owner Password Credentials): используются доверенными приложениями, например приложениями, которые являются частью самого сервиса.
  • Учётные данные клиента (Client Credentials): используются при доступе приложения к API.

Далее мы рассмотрим эти типы разрешения на авторизацию, примеры их использования.

Регистрация приложения

Перед тем, как начать использовать OAuth в вашем приложении, вам необходимо зарегистрировать своё приложения в сервисе. Это делается путём регистрации в разделе “developer” или “API” сайта сервиса, где вам необходимо предоставить следующую информацию (возможно, включая некоторые детали о вашем приложении):

  • Название приложения
  • Сайт приложения
  • Redirect URL или callback URL

Redirect URL – это URL, на который сервис будет перенаправлять пользователя после авторизации (или отказа в авторизации) вашего приложения.

Роли oauth

OAuth определяет четыре роли:

  • Владелец ресурса
  • Клиент
  • Сервер ресурсов
  • Авторизационный сервер

Далее мы рассмотрим каждую из ролей.

Сервер ресурсов / авторизации: api

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

С точки зрения разработчика приложения API сервиса одновременно выполняет и роль сервера ресурсов и роль сервера авторизации. Далее мы будем считать эти две роли одной, и называть её Сервис или API.

Серверная авторизация


Серверная авторизация гораздо веселее. Механизм серверной авторизации для доступа к ВКонтакте API с стороннего сайта создан на базе протокола OAuth 2.0.

Процесс авторизации происходит следующим образом:

  1. Необходимо в браузере пользователя показать страницу, где он разрешит приложению доступ к своим данным;
  2. После успешной авторизации приложения браузер пользователя будет перенаправлен по адресу REDIRECT_URI, указанному при открытии диалога авторизации. При этом в GET-параметре будет передан код для получения ключа доступа;
  3. Необходимо выполнить запрос с передачей кода и секретных данных приложения на специальный адрес, в ответ мы получим ключ доступа access_token, необходимый нам для совершения запросов к API.

К сожалению, второй пункт, а именно, перенаправление браузера, срабатывает далеко не всегда, точнее, не срабатывает нигде, кроме ИЕ, в тот самый первый раз, когда пользователь впервые разрешает приложению доступ к своим данным. И как только не извращаются разработчики, лишь бы получить заветный код.


К тому же, открытие дополнительного окна опять-таки связано с опасностью, что оно будет заблокировано.

К счастью, если мы знаем ссылку, по которой нужно открыть окно, есть много хороших способов открыть его так, чтобы оно не было заблокировано. Например, старый добрый тег iframe всё ещё в строю стандарта HTML, и он как нельзя лучше подходит для нашей задачи.

Во-первых, раз мы знаем адрес, то мы можем и открыть фрейм и показать пользователю ссылку, где можно всё проделать вручню.

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

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

Реализацию класса доступа к VK API по OAuth на PHP можно легко найти на гитхабе, что я и сделал (PHP >= 5.4).(Совершенно случайно оказалось, что этот класс, скорее всего, написан хабрапользователем vladkens, за что ему огромное спасибо)

Теперь перейдём к самому интересному.

Реализуем серверный скрипт, который будет отвечать на AJAX-запросы нашей страницы

<?php
 require_once('vk.php');
 session_start();
 
 function getPostStr($data, $default = false){ // возврачает строку - значение $_POST[$data]
  if(!isset($_POST[$data])) return $default;
  $data = $_POST[$data];
  $data = htmlspecialchars(strip_tags(trim($data)));
  return ($data != "" ? $data : $default);
 }
 function error($code, $text, $params = Array()){
  $result = Array('error' => Array('code' => $code, 'message' => $text));
  if(count($params) > 0) foreach($params as $key => $value) $result['error'][$key] = $value;
  die(json_encode($result));
 }
 
 $vkConf = Array(
  'appID'       => YOUR_APP_ID,
  'apiSecret'   => YOUR_API_SECRET,
  'callbackUrl' => YOUR_DOMAIN . '/auth.php',
  'apiSettings' => YOUR_APP_PERMISSIONS
 );
 
 $vk = (isset($_SESSION['accessToken']))
        ? new VK($vkConf['appID'], $vkConf['apiSecret'], $_SESSION['accessToken']) : null;
 
 function userIn($vk, $vkConf){ // Авторизация пользователя
  unset($_SESSION['accessToken']);
  $vk = new VK($vkConf['appID'], $vkConf['apiSecret']);
  $authorizeUrl = $vk -> getAuthorizeURL($vkConf['apiSettings'], $vkConf['callbackUrl']);
  error(-1, "Необходима авторизация ВКонтакте!", Array('url' => $authorizeUrl));
 }
 function getFriends($fields, $order, $vk){ // Получение списка друзей пользователя
  $userFriends = $vk -> api('friends.get', array('fields' => $fields, 'order' => $order));
  $result = Array();
  foreach($userFriends['response'] as $key => $value){
   $result[] = Array('firstName' => $value['first_name'], 'lastName' => $value['last_name'], 'uid' => $value['uid']);
  }
  echo json_encode($result);
 }

 $method = strtolower($api -> getStr("method"));

 switch($method){
  case "user.in"     : userIn($vk, $vkConf); break;
  case "friends.get" : getFriends(getPostStr("fields"), getPostStr("order"), $vk); break;
  default: Api::error(0, "Неверный запрос к Api");
 }
?>

Запросы на эту страницу мы будем посылать методом POST, где параметр

method

содержит название метода, который мы хотим выполнить на сервере.

Структура проекта и установка зависимостей


Для начала создадим структуру папок для проекта, которая, в самом начале работы, должна выглядеть следующим образом:

Авторизация на сайте через API социальных сетей с интеграцией в Spring Security / Хабр
Структура папок API

В ходе продвижения по материалу мы значительно расширим эту структуру.

Теперь нужно установить несколько зависимостей. Для этого перейдём в корневую папку проекта (здесь это focus-budget-manager) и, предварительно сформировав package.json командой npm init, выполним следующую команду:

npm i --save express body-parser mongoose consign cors bcrypt jsonwebtoken morgan passport passport-jwt module-alias

Рассмотрим некоторые из этих зависимостей и их роль в проекте:

Тестирование приложения с использованием postman


Для начала подключимся к конечной точке

setup

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

Тип разрешения на авторизацию: неявный

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

Это, в свою очередь, делает токен доступным пользователю и другим приложениям на устройстве пользователя. Также при этом типе разрешения на авторизацию не осуществляется аутентификация подлинности приложения, а сам процесс полагается на URL перенаправления (зарегистрированный ранее в сервисе).

Неявный тип разрешения на авторизацию не поддерживает токены обновления токена доступа (refresh tokens).

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

Тип разрешения на авторизацию: учётные данные владельца ресурса

При этом типе разрешения на авторизацию пользователь предоставляет приложению напрямую свои авторизационные данные в сервисе (имя пользователя и пароль). Приложение, в свою очередь, использует полученные учётные данные пользователя для получения токена доступа от сервиса.

Этот тип разрешения на авторизацию должен использоваться только в том случае, когда другие варианты не доступны. Кроме того, этот тип разрешения стоит использовать только в случае, когда приложение пользуется доверием пользователя (например, является частью самого сервиса, или операционной системы пользователя).

Тип разрешения на авторизацию: учётные данные клиента

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

Файл budgetmanagerapi/app/api/auth.js

Теперь начинаем создавать некоторые из методов API. Перейдём в папку

BudgetManagerAPI/app

, создадим в ней директорию

api

, а в ней — файл

auth.js

. Запишем в него следующее:

const mongoose = require('mongoose'),
      jwt = require('jsonwebtoken'),
      config = require('@config');


Обратите внимание на то, что, благодаря использованию модуля

module_alias

мы сделали код чище. Иначе пришлось бы писать примерно следующее:

config = require('./../../config);

Теперь, после подключения пакетов, сделаем в том же файле следующее:

Файл budgetmanagerapi/app/setup/index.js


После настройки псевдонимов перейдём в папку

BudgetManagerAPI/app

и создадим новую папку

setup

, а в ней — файл

index.js

. Добавим в него следующее:

Файл budgetmanagerapi/config/app.js


В директории

BudgetManagerAPI/config

создадим файл

app.js

. Для начала подключим зависимости:

const express = require('express'),
      app = express(),
      bodyParser = require('body-parser'),
      mongoose = require('mongoose'),
      morgan = require('morgan'),
      consign = require('consign'),
      cors = require('cors'),
      passport = require('passport'),
      passportConfig = require('./passport')(passport),
      jwt = require('jsonwebtoken'),
      config = require('./index.js'),
      database = require('./database')(mongoose, config);


В строке

passportConfig = require('./passport')(passport)

мы импортируем конфигурационный файл для

passport

, передавая

passport

в качестве аргумента, так как в

passport.js

имеется такая команда:

Благодаря такому подходу мы можем работать с passport внутри файла passport.js без необходимости подключать его.

Далее, в файле app.js, начинаем работу с пакетами и устанавливаем секретный ключ:

app.use(express.static('.'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(morgan('dev'));
app.use(cors());
app.use(passport.initialize());

app.set('budgetsecret', config.secret);

Как вариант, вместо использования пакета

cors

, можно сделать следующее:

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

Файл budgetmanagerapi/config/database.js


В папке

BudgetManagerAPI/config

создадим файл

database.js

, который ответственен за работу с базой данных. Добавим в этот файл следующее:

module.exports = (mongoose, config) => {
  const database = mongoose.connection;
  mongoose.Promise = Promise;
  mongoose.connect(config.database, {
    useMongoClient: true,
    promiseLibrary: global.Promise
  });
  database.on('error', error => console.log(`Connection to BudgetManager database failed: ${error}`));
  database.on('connected', () => console.log('Connected to BudgetManager database'));
  database.on('disconnected', () => console.log('Disconnected from BudgetManager database'));
  process.on('SIGINT', () => {
    database.close(() => {
      console.log('BudgetManager terminated, connection closed');
      process.exit(0);
    })
  });
};

Тут мы сначала переключили

mongoose

на использование стандартного объекта

Promise

. Если этого не сделать, можно столкнуться с предупреждениями, выводимыми в консоль. Затем мы создали стандартное подключение

mongoose

Файл budgetmanagerapi/config/index.js


Создадим в папке

BudgetManagerAPI/config

файл

index.js

и внесём в него следующий код:

module.exports = {
  secret: 'budgetsecret',
  session: { session: false },
  database: 'mongodb://127.0.0.1:27017/budgetmanager'
}


Этот файл содержит параметры подключения к базе данных и секретный ключ, который мы используем для создания JWT-токенов.

Здесь предполагается работа с локальным сервером MongoDB. При этом в строке 127.0.0.1:27017 можно использовать localhost. Если хотите, можете работать с облачной базой данных MongoDB, созданной, например, средствами MLabs.

Файл budgetmanagerapi/config/passport.js

После того, как модель

Шаг 1: ссылка для неявной авторизации

При неявном типа разрешения на авторизацию пользователю предоставляется ссылка, запрашивающая токен у API. Эта ссылка выглядит почти так же, как ссылка для предыдущего способа (с кодом авторизации), за исключением того, что запрашивается токен вместо кода (обратите внимание на response type “token”):

Шаг 1: ссылка с кодом авторизации

Сначала пользователю предоставляется ссылка следующего вида:

Шаг 3: пользовательский агент получает токен доступа с uri перенаправления

Если пользователь выбирает “Авторизовать приложение”, сервис перенаправляет пользовательский агент по URI пренправления приложения и включает в URI фрагмент, содержащий токен доступа. Это выглядит примерно вот так:

Шаг 4: пользовательский агент следует по uri перенаправления

Пользовательский агент следует по URI перенаправления, сохраняя при этом токен доступа.

Шаг 4: приложение запрашивает токен доступа

Приложение запрашивает токен доступа у API путём отправки авторизационного кода и аутентификационной информации (включая секрет клиента) сервису. Ниже представлен пример POST-запроса для получения токена DigitalOcean:

Шаг 5: приложение выполняет скрипт извлечения токена доступа

Приложение возвращает веб-страницу, которая содержит скрипт для извлечения токен доступа из полного URI перенаправления, сохранённого пользовательским агентом.

Шаг 5: приложение получает токен доступа

Если авторизация прошла успешно, API возвращает токен доступа (а также, опционально, токен для обновления токена доступа – refresh token). Весь ответ сервера может выглядеть следующим образом:

Шаг 6: токен доступа передаётся приложению

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

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

Итоги

На этом мы завершаем первую часть данной серии. Здесь вы узнали о том, как, с чистого листа, создать Node.js-приложение и настроить простую JWT-аутентификацию. В следующей части приступим к разработке пользовательского интерфейса приложения с использованием Vue.js.

Уважаемые читатели! Как по-вашему, подойдёт ли способ аутентификации пользователей, предложенный автором, для использования в продакшне?

Заключение

В этой статье рассмотрели один из примеров реализации REST приложения с Spring Security и JWT. Надеюсь данный вариант реализации кому то окажется полезным.

Полный код проекта выложен доступен на github

Похожее:  Горячая линия Optima (Оптима): как связаться, войти в личный кабинет, вернуть товар, написать жалобу в службу поддержки

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *