Введение

LoQ (Login via QR) - это функция для идентификации и аутентификации пользователя на веб-сайте исключительно с помощью QR-кода на странице входа. Пользователю не нужно вводить логин, пароль или какие-либо одноразовые пароли.

Пользователю нужно лишь отсканировать QR-код с помощью мобильного приложения и подтвердить операцию входа в систему.

В то же время пользователь будет идентифицирован по своему идентификатору PC из мобильного приложения, а аутентификация будет осуществляться с помощью цифровой подписи и функции подтверждения транзакций в PC.

Как это работает можно посмотреть на демо-портале PC.

login_page.png

Процесс идентификации и аутентификации

В процессе идентификации и аутентификации пользователя с использованием функции LoQ участвуют как минимум 8 компонентов.

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

Предварительные требования

  1. Функция LoQ должна быть активирована и установлена в инфраструктуре заказчика (либо может использоваться облачная установка)
  2. Веб-приложение должно быть связано с системой PC (пользователи веб-приложения должны использовать PC для подтверждения транзакций).

Описание процесса

Диаграмма процесса показана на рисунке 1 (рекомендуется увеличить изображение).

alt text

Участники процесса

Name              Functions
Бэкенд веб-приложения Бэкенд веб-приложения обрабатывает запросы пользователей. В рабочем процессе должен запрашивать LoQ JS у компонента LoQ Internal, размещать его на странице входа и предоставлять пользователю
Веб-браузер Веб-браузер на стороне пользователя обрабатывает LoQ JS, отображает QR-код для входа и, после успешной идентификации и аутентификации пользователя, перенаправляет его в авторизованную зону
LoQ JS (в браузере) JavaScript-скрипт, который выполняет следующие действия:
- запрашивает QR-код и параметры сессии у LoQ External с заданным интервалом (по умолчанию — каждую 1 минуту)
- проверяет у LoQ External, был ли пользователь уже идентифицирован и аутентифицирован, с заданным интервалом (по умолчанию — каждую секунду)
- получает и перерисовывает QR-код на странице входа или выполняет перенаправление на ссылку авторизации веб-приложения после успешной аутентификации
LoQ External Компонент для взаимодействия с LoQ JS и PC Mobile SDK. Должен быть размещён в демилитаризованной зоне (DMZ) с доступом из/в Интернет
LoQ Internal Компонент для взаимодействия с бэкендом веб-приложения и PC Server. Генерирует и обрабатывает параметры сессии во время идентификации и авторизации пользователя. Выполняет callback-запрос к бэкенду веб-приложения для получения PC User ID идентифицированного и аутентифицированного пользователя в сессии. Должен быть размещён во внутренней сети без доступа к Интернету
PC Server Компонент для создания и подтверждения транзакции с целью аутентификации пользователя
Мобильное приложение Вызывает методы PC Mobile SDK, предоставляет пользовательский интерфейс на мобильном телефоне
PC Mobile SDK Обрабатывает рабочий процесс LoQ на стороне мобильного устройства:
- считывает значение QR-кода с параметрами сессии
- отправляет запрос к LoQ External с указанием идентификатора пользователя PC
- обрабатывает подтверждение транзакции (цифровую подпись) для аутентификации пользователя

Шаги процесса (по схеме)

  1. Когда бэкенд веб-приложения получает запрос на страницу входа (новый пользователь открывает веб-сессию), он обращается к LoQ Internal за LoQ JS, указывая необходимые параметры: PC system_id, текст сообщения для входа, URL для callback-уведомления после идентификации и аутентификации пользователя, а также URL для перенаправления браузера в авторизованную зону.
    LoQ JS будет содержать всю необходимую информацию для взаимодействия с LoQ External, отрисовки QR-кода для входа и проверки статуса аутентификации пользователя.
    • Бэкенд веб-приложения должен реализовать callback-эндпоинт по адресу callback_url для получения данных об идентифицированном и аутентифицированном пользователе и идентификаторе транзакции. На основе этой информации Бэкенд веб-приложения должен авторизовать пользователя.
    • Бэкенд веб-приложения должен реализовать редирект-эндпоинт по адресу redirect_url. По этому адресу браузер будет перенаправлен скриптом LoQ JS после успешной аутентификации. Перед этим будет отправлен callback на callback_url.
  2. Бэкенд веб-приложения встраивает LoQ JS в страницу входа и отправляет её в Веб-браузер пользователя.
  3. С этого момента LoQ JS начинает взаимодействовать с LoQ External: отрисовывает и обновляет QR-код для входа, а также проверяет статус аутентификации пользователя.
  4. При каждом запросе от LoQ JS компонент LoQ External проверяет данные сессии, при необходимости обновляет QR-код и отправляет команду перенаправления, если пользователь был аутентифицирован.
  5. После отображения QR-кода в Веб-браузер, пользователь может отсканировать его с помощью Мобильного приложения для запуска процесса идентификации и аутентификации.
  6. Значение QR-кода (после декодирования) передаётся в PC Mobile SDK, после чего может быть запущен процесс аутентификации:
    • вызов метода PCLogin.importFromJSON(…)
    • определение, может ли один из зарегистрированных пользователей PC быть использован для входа (пользователи должны быть зарегистрированы в той же системе PC)
    • запрос транзакции входа для аутентификации пользователя
  7. На основе запроса от PC Mobile SDK с идентификатором пользователя, LoQ External обращается к LoQ Internal для создания транзакции PC через PC Server. Эта транзакция должна быть подписана для аутентификации пользователя.
  8. Идентификатор транзакции PC (PC Transaction ID) передаётся через LoQ Internal и LoQ External обратно в PC Mobile SDK.
  9. После этого PC Mobile SDK запрашивает данные транзакции и передаёт их в Мобильное приложение для отображения пользователю.
  10. Если пользователь соглашается войти в систему, Мобильное приложение с помощью PC Mobile SDK подписывает транзакцию.
  11. После подписания и верификации транзакции, PC Server отправляет callback-уведомление в LoQ Internal.
  12. LoQ Internal отправляет callback в Бэкенд веб-приложения с указанием PC User ID и PC Transaction ID в рамках текущей сессии.
  13. Бэкенд веб-приложения должен по внутренним правилам зафиксировать, что:
    • в сессии с указанным session id
    • пользователь с указанным pc user id
    • аутентифицирован путём подписания транзакции с указанным pc transaction id
  14. При следующем запросе от LoQ JS, LoQ External ответит, что Веб-браузер должен быть перенаправлен на redirect_url с параметром session_id.
  15. LoQ JS перенаправляет браузер на redirect_url Бэкенда веб-приложения, и Бэкенд веб-приложения получает из запроса session_id.
  16. Бэкенд веб-приложения проверяет, какой пользователь был идентифицирован и аутентифицирован в сессии с указанным session id (шаг 13).
  17. Если пользователь найден, Бэкенд веб-приложения авторизует веб-сессию для доступа к защищённой зоне.
  18. Опционально, Бэкенд веб-приложения может получить всю информацию о подтверждении транзакции (значение цифровой подписи, данные мобильного устройства, детали транзакции и т.д.) от PC Server, используя идентификатор транзакции.

Интеграция и API

Получение LoQ JS

Запрос LoQ JS

endpoint: {{loq-internal-url}}/system/loginUser
method: POST
content-type: application/json

Тело запроса

{
    "systemId" : "{{pc-system-id}}",
    "callback" : "{{callback-url}}",
    "redirect" : "{{redirect-url}}",
    "sessionTimeout" : 6000,
    "qrTimeout" : 600,
    "size" : 300,
    "text": "Please, confirm your login to the best web site",
    "textRenderType": "raw",
    "qrUrlPrefix": "paycontrol",
    "deepLinkPrefix": "paycontrol",
    "deepLinkOnly": false,
    "pcTransactionAppExtra": "{\"app_callback_deeplink\": \"https://abs.absnet.loc/login_success\"}"
}

Пример ответа с JS-скриптом и ID сессии

{
    "jsScript": "[Здесь будет размещён JS-скрипт (см. пример ниже)]",
    "sessionId": "7db47b87-8107-4d25-a967-9210e0d50b20]"
}

JS по умолчанию для LoQ

// LoQ JS Script
getQr = function () {
    var xhr = new XMLHttpRequest();
    var urlLoq = 'https://dev.paycontrol.org/api5/loqr/ext/site/status';
    var body = {
        sessionId: "1e9a02b9-8827-4df7-a518-923007430872"
    };

    var loginQR;
    var loginDeepLink;
    var isRefreshRequired;

    makeRequest();

    function makeRequest() {
        let data = JSON.stringify(body);
        let response;

        xhr.open('POST', urlLoq);
        xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
        xhr.setRequestHeader('Cache-Control', 'no-cache');
        xhr.setRequestHeader('Pragma', 'no-cache');
        xhr.send(data);

        xhr.onload = function (e) {
            if (this.status === 200 || this.status === 201) {
                response = JSON.parse(e.target.response);
                isRefreshRequired = response.isRefreshRequired;
                loginQR = response.loginQR;
                loginDeepLink = response.loginDeepLink;

                checkQR(response);
                console.log(response);
            } else {
                console.log(e.target.onerror);

            }

            setTimeout(makeRequest, 1000);
        }
        ;
    }

    function checkQR(response) {
        if (response.redirect != null) {
            loginQR = null;
            document.getElementById('pc_lo_qr').src = "";

            var redirectForm = document.createElement('form');
            var sessionId = document.createElement('input');

            document.body.append(redirectForm);
            redirectForm.append(sessionId);
            redirectForm.setAttribute('action', response.redirect.url);
            redirectForm.setAttribute('method', 'post');
            redirectForm.setAttribute('hidden', 'true');
            sessionId.setAttribute('name', 'sessionId');
            sessionId.setAttribute('id', 'sessionId');
            sessionId.setAttribute('type', 'hidden');
            sessionId.setAttribute('value', response.redirect.sessionId);
            redirectForm.submit();
        }

        if (response.isRefreshRequired || typeof (loginQR) == undefined) {
            loginQR = response.loginQR;
            loginDeepLink = response.loginDeepLink;
            document.getElementById('pc_lo_qr').src = "data:image/gif;base64," + loginQR;

            if (null != loginDeepLink) {
                document.getElementById('pc_lo_deeplink').href = loginDeepLink;
            }
        }
    }
};

Этот запрос генерирует JS-скрипт со всеми необходимыми параметрами для работы на стороне веб-браузера.

Параметр Обязательный Описание
systemId да ID системы PC
callback да URL для callback-уведомления с информацией об идентифицированном и аутентифицированном пользователе PC
redirect да Редирект
sessionTimeout да Время жизни сессии входа в секундах. После истечения этого времени сессия не может быть использована для входа
qrTimeout да Время жизни QR-кода. После истечения этого времени QR-код будет автоматически сгенерирован компонентом LoQ External
size да Размер QR-кода в пикселях
text нет Текст транзакции входа. Этот текст будет использован для создания транзакции PC и должен быть показан пользователю в мобильном приложении. Если текст не указан, будет создана «пустая» транзакция. Рекомендуется включать в текст информацию об ОС, версии браузера и времени входа.
textRenderType нет Способ отображения текста text в мобильном приложении. Может быть raw или markdown. Значение по умолчанию — raw
qrUrlPrefix нет Если задано это значение, в QR-код будет закодирован deep-link с указанным протоколом (префиксом). Это будет полезно, если ваше приложение поддерживает deep-links и пользователь использует сканер QR-кодов (например, приложение "Камера" на iOS).
Deep-link в QR будет иметь вид [qrUrlPrefix]://loq?login_json=[json to be provided to PC Mobile SDK]
deepLinkPrefix нет Если задано это значение, помимо QR-кода, LoQ сгенерирует deep-link для отображения на мобильной версии страницы входа. Это позволяет показывать QR-код для десктопной версии и deep-link — для мобильной. Пользователь может тапнуть по deep-link для запуска процесса входа (ваше мобильное приложение должно обрабатывать deep-links)
Deep-link будет иметь вид [deepLinkPrefix]://loq?login_json=[json to be provided to PC Mobile SDK]
Обратите внимание, что JS будет отдельно размещать QR-код и deep-link в HTML DOM (см. JS по умолчанию).
deepLinkOnly нет Если true, QR-код не будет сгенерирован — будет предоставлен только deep-link. Полезно при запросе входа из мобильного браузера
pcTransactionAppExtra нет Это значение будет передано в транзакцию на PC Server в процессе входа и предоставлено мобильному приложению. См. Create Transaction Request
Это значение может содержать переменную %SESSION_ID% в дополнение к переменным, заданным на PC Server.

В ответе возвращается JS-скрипт, который должен быть встроен в страницу входа внутри html-тега <script></script>.

Содержимое страницы входа

Пример JS-кода для запуска LoQ

// если LoQ JS успешно встроен
if (typeof getQr === "function") { 
    getQr();
}

По умолчанию LoQ JS пытается найти в структуре HTML DOM объект pc_lo_qr типа img и вставить его в значение свойства src полученного QR-кода.
Если задан параметр deepLinkPrefix, то по умолчанию LoQ JS пытается найти объект pc_lo_deeplink типа a и вставить deep-link в свойство href.

Чтобы запустить процесс взаимодействия между LoQ JS и LoQ External, на странице входа должна быть выполнена функция с именем по умолчанию getQr (см. пример).

LoQ JS можно настроить или заменить (при необходимости) в процессе встраивания LoQ. В этом случае названия функций и объектов могут отличаться.

Требования к получателю Callback

Содержимое callback

endpoint: {{callback_url}}
method: POST
content-type: application/json
{ 
   "pc_callback":{ 
      "loqr_callback":{ 
         "sessionId":"25552734-a388-4f30-8517-2b67598f2f75",
         "pcUserId":"c1d656e9-481b-4244-ade1-f36ec10eff25",
         "transactionId":"bf6c447a-0f82-4bd3-a49f-0c460bea4fcc"
      },
      "type":"loqr_callback",
      "result":{ 
         "error_message":"Success",
         "error_code":0
      },
      "version":3
   }
}

После идентификации и аутентификации пользователя LoQ Internal отправит коллбэк на бэкенд веб-приложения по адресу callback_url, указанному в запросе Get LoQ JS.

Параметр Обязательный Описание
pc_callback.type да Значение должно быть loqr_callback
pc_callback.loqr_callback да Данные об идентифицированном и аутентифицированном пользователе
pc_callback.loqr_callback.sessionId да ID сессии LoQ, использованной для аутентификации пользователя
pc_callback.loqr_callback.pcUserId да ID аутентифицированного пользователя в системе PC
pc_callback.loqr_callback.transactionId да ID транзакции PC, подписанной аутентифицированным пользователем.
pc_callback.result да Error-описание
pc_callback.version да Версия callback

Требования к обработчику редиректа

Стандартный скрипт для выполнения редиректа

// очистить значение QR-кода
document.getElementById('pc_lo_qr').src = "";

// создать HTML-форму
var redirectForm = document.createElement('form');
document.body.append(redirectForm);
redirectForm.setAttribute('action', response.redirect.url);
redirectForm.setAttribute('method', 'post');
redirectForm.setAttribute('hidden', 'true');

// добавить поле с именем sessionId
var sessionId = document.createElement('input');
sessionId.setAttribute('name', 'sessionId');
sessionId.setAttribute('id', 'sessionId');
sessionId.setAttribute('type', 'hidden');

// установить значение sessionId, полученное от LoQ External
sessionId.setAttribute('value', response.redirect.sessionId);

redirectForm.append(sessionId);

// отправить форму для выполнения редиректа
redirectForm.submit();

После того как LoQ JS получит информацию об аутентификации пользователя, он перенаправит веб-браузер на redirect_url, указанный в запросе Get LoQ JS.

Стандартный метод редиректа LoQ - это js-скрипт, который встраивает html-форму в модель HTML DOM и отправляет ее.
Перед этим скрипт удаляет значение QR-кода.

Таким образом, если вы используете метод редиректа по умолчанию, получатель редиректа на стороне бэкенда веб-приложения должен обрабатывать запрос с типом содержимого application/x-www-form-urlencoded.

JS-скрипт для редиректа можно настроить или заменить при необходимости в процессе установки LoQ.

Требования к мобильному приложению

Мобильное приложение должно реализовывать процесс LoQ в соответствии со схемой.

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

Пример алгоритма входа в систему с помощью QR-кода (или deep-link):

  1. Отсканируйте QR-код (или воспользуйтесь deep-link) и извлечь JSON, содержащий все необходимые данные
  2. Вызовите importFromJson(String, GetLoginDataCallback) для создания объекта PCLogin. Созданный объект будет возвращен в качестве параметра метода success().
  3. Вызовите suggestUsers() для созданного объекта, чтобы получить ключи, которые можно использовать для входа в систему
  4. После выбора ключа вызовите requestLoginTransaction(PCUser, GetLoginTransactionCallback) с выбранным PCUser из списка, предложенного функцией suggestUsers()
  5. После вызова PCLogin.GetLoginTransactionCallback.success(PCTransaction) вы можете отобразить данные транзакции в приложении. Обратите внимание, что PCTransaction.getTransactionText() может возвращать либо null, либо текст транзакции. Не пытайтесь подписать эту транзакцию вручную. Вместо этого вызовите confirm(PCGeneralCallback) для завершения входа в систему или decline(PCGeneralCallback) для отмены входа.

Более подробную информацию можно найти в PC Mobile SDK.

Также можно использовать мобильное приложение PC в качестве примера.

Интеграция в инфраструктуру

Взаимодействие компонентов

LoQ состоит из двух частей: LoQ External и LoQ Internal.

Функции этих элементов есть описаны в разделе "Рабочий процесс".

Схема взаимодействия участников процесса входа в систему представлена на рисунке 2.

alt text

Варианты поставки серверных компонентов

Компоненты сервера LoQ поставляются в виде Java-приложения (jar-файла) и инструментов для запуска в качестве службы (на базе Linux или Windows).

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

Типовая конфигурация сервера

Типовой сервер (или контейнер) включает следующие установленные компоненты:

Компонент Описание
Операционная система На базе Linux
Рабочая среда сервера приложений Java 8/11

Все компоненты LoQ запускаются автоматически вместе с операционной системой. Ручная настройка запуска/остановки не требуется.

Если не предоставляются ни физические, ни виртуальные серверы, подготовка ОС выполняется заказчиком. Подготовка включает:

  • установку операционной системы;
  • корректную настройку записей DNS;
  • установку Java Runtime Environment;
  • подготовку TLS-сертификатов (при необходимости).

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

Компонент Описание
Операционная система Microsoft Windows
Рабочая среда сервера приложений Java 8/11

Отказоустойчивость и масштабирование

Возможности отказоустойчивости и масштабирования аналогичны компонентам PC Server.

Установка и запуск

Перед установкой

Сначала необходимо развернуть и предоставить доступ к одной и той же базе данных Redis для компонентов Internal и External LoQ.

Настраиваемые параметры

Ниже приведены примеры файлов application.yml. Все параметры, кроме logging, должны быть заданы:

LoQR Int

Пример application.yml для LoQR Internal

server:
  port: 8098
  address: 0.0.0.0
loqr:
  parametrs:
    qr:
      width: 120
      typeImg: "gif"
  pc:
    url: "http://pc:8080/pc-api/"
  url: "http://loqrint:8098"
  urlExternal: "https://example.com/loqr/ext"
  timeouts:
    QR: 120
    session: 600
spring:
  redis:
    host: redis
    port: 6379
    database: 1
    jedis:
      pool:
       min-idle: 5
       max-active: 100
       max-idle: -1
       max-wait: 10000
#logging:
#  level:
#    tech.paycon: DEBUG
  • loqr.pc.url — эндпоинт API PCS;
  • loqr.url — адрес LoQR Internal. Используется для получения коллбэка от PC при подтверждении транзакции входа;
  • loqr.urlExternal — Внешний адрес LoQR. Используется в браузере конечного пользователя для получения статуса сеанса и обновления QR-кода;
  • spring.redis.host — Redis host;
  • spring.redis.database — Redis DB.

LoQR Ext

Пример application.yml для LoQR External

server:
  port: 8099
  address: 0.0.0.0
loqrExternal:
  loqr: "http://loqrint:8098"
  logoQRPath: "pc_logo.png"
  url: "https://example.com/loqr/ext"
  logoOn: true
spring:
  redis:
    host: redis
    port: 6379
    database: 1
    jedis:
      pool:
        min-idle: 10
        max-active: 60
        max-idle: -1
        max-wait: -1
# logging:
#   level:
#    tech.paycon: DEBUG
  • loqrExternal.loqr — адрес Internal LoQR. Используется Internal LoQR для создания транзакции входа;
  • loqrExternal.url — адрес External LoQR. Этот адрес будет добавлен в QR-код для вызова LoQR External из Mobile SDK при создании транзакции входа;
  • spring.redis.host — Redis host;
  • spring.redis.database — Redis DB.

Запуск в контейнерах

Образы включают JRE 17 и JAR-файлы модулей LoQR.

Образы

  • LoQR Internal: registry.paycontrol.org/loqr/pc_loqr_int:1.0
  • LoQR External: registry.paycontrol.org/loqr/pc_loqr_ext:1.0

Переменные окружения

Обязательные переменные окружения, которые необходимо передать контейнерам (или смонтировать файл application.yml):

LoQR Int

  • LOQR_PC_URL — эндпоинт API PCS;
  • LOQR_URL — адрес LoQR Internal. Используется для callback от системы PC при подтверждении транзакции входа;
  • LOQR_URLEXTERNAL — адрес LoQR External. Используется JS в браузере конечного пользователя для опроса статуса сессии и обновлений QR;
  • SPRING_REDIS_HOST — Redis host;
  • SPRING_REDIS_DATABASE — Redis DB.

LoQR Ext

  • LOQREXTERNAL_LOQR — адрес Internal LoQR. Используется Internal LoQR для создания транзакции входа;
  • LOQREXTERNAL_URL — адрес External LoQR. Этот адрес будет добавлен в QR-код для вызова LoQR External из Mobile SDK при создании транзакции входа;
  • SPRING_REDIS_HOST — Redis host;
  • SPRING_REDIS_DATABASE — Redis DB.

Добавление доверенных сертификатов

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

Добавление сертификатов ЦС

    volumes:
      - ca-certs-storage:/certificates:ro
    environment:
      - TRUST_CERT=/certificates/OwnCA.crt;/certificates/testCA.crt