Аутентификация через OAuth 2.0
В этой статье:
Общие сведения
OAuth 2.0 - это протокол авторизации для предоставления доступа сторонним приложениям к защищенным ресурсам.
Протокол используется для аутентификации пользователей. Сторонним приложением является Universe MDM, защищенным ресурсом – информация о пользователе.
OAuth 2.0 предполагает разные способы получения токена доступа сторонним приложением. Самым безопасным и общепринятым стандартом для веб-приложений является Authorization Code Grant. Добавлена поддержка только этого способа.
Аутентифицированные пользователи создаются OAuth 2.0 провайдером (имплементацией интерфейса из SDK backend) после получения информации о пользователе из внешней системы (логин/email/фио/имена ролей/т.п.).
Авторизации OAuth 2.0 в Universe MDM
Пример добавления OAuth 2.0 провайдера вместе с инструкцией по использованию можно найти здесь.
Предусловие:
Присутствует зарегистрированный клиент (веб-приложение Universe MDM) на сервере авторизации.
Клиент указал корректный redirect URI (callback URI), который должен перенаправить запрос пользователя на GET
http://your-universe-mdm.org/universe-backend/api/v2/core/authentication/login?source=oauth2
В Universe MDM на backend должен быть зарегистрирован OAuth 2.0 provider. Это может быть сделано двумя способами:
Вручную в коде из одного модулей, вызовом метода
com.universe.mdm.rest.v1.oauth2.service.OAuth2Service#register
.Автоматически через загрузку библиотеки с классом, который реализует интерфейс
com.universe.mdm.rest.v1.oauth2.type.OAuth2Provider
, согласно контракту, который указан в Javadoc этого интерфейса (интерфейс находится в SDK).
Такие библиотеки на экране библиотек будут иметь "OAuth 2.0 провайдер" в колонке "Тип".
При удалении этой библиотеки этот OAuth 2.0 провайдер будет удален (т.е. аутентифицироваться через него будет нельзя).
При перезагрузке Universe MDM все имеющиеся библиотеки с типом "OAuth 2.0 провайдер" будут автоматически регистрировать свои провайдеры.
Интерфейс OAuth2Provider содержит следующие методы:
getId(): String - возвращает ID провайдера;
getClientId(): String - возвращает client ID веб-приложения на сервере авторизации;
getClientSecret(): String - возвращает client secret веб-приложения на сервере авторизации;
getScope(): String - возвращает список разрешений, требуемых веб-приложениям для получения информации о пользователе (необязательная часть, зависит от сервера авторизации);
isAuthRequest(HttpRequest): boolean - метод должен вернуть true, если запрос к backend является запросом, инициирующим авторизацию через этот провайдер;
getAuthorizationUri(): String - возвращает URI для авторизации, по которому будет перенаправлен пользователь для подтверждения своего желания предоставить свои данные веб-приложению и для получения кода авторизации;
getTokenUri(): String - возвращает URI для получения токена доступа веб-приложения, используя полученный код авторизации;
isBasicAuthorizationForTokenRequest(): boolean - определяет способ авторизации в запросе получения токена доступа (передавать client secret через Basic Authorization Scheme или через form);
getUserInfoUri(): String - возвращает URI для получения информации о пользователе с сервера ресурсов, используя полученный токен доступа (сервер ресурсов и сервер авторизации могут быть и разными серверами, и одним сервером);
extractUserInfo(HttpResponse): OAuth2UserInfo - конвертирует ответ запроса получения информации о пользователе в объект, с которым будет работать веб-приложение.
Схема работы
С точки зрения backend

Для старта аутентификации через OAuth 2.0 должен быть послан на backend [любой запрос, который требует авторизации, или запрос логина] с параметрами sso=true и oauth2=<providerId> в query string.
Для всех зарегистрированных провайдеров будет проверено равенство значения query параметра oauth2 и ID провайдера:
Если ID ни одного провайдера не равно значению query параметра oauth2, то будет прислан ответ об отсутствии токена авторизации.
Первый провайдер, ID которого равно значению query параметра oauth2, будет использован для ответа с перенаправлением (HTTP code 302) на сервер авторизации.
Перенаправление будет по адресу getAuthorizationUri() провайдера, с указанием client ID, scope, state (случайно сгенерированная строка) и redirect URI (из параметра
com.universe.mdm.rest.v1.oauth2.redirect.address
, если он там указан).
Пользователь, перенаправленный на сервер авторизации, должен выдать разрешение на доступ к своим данным, в результате чего сервер авторизации перенаправит его обратно в веб-приложение:
Если пользователь однажды уже выдавал разрешение, то повторное разрешение может не требоваться (зависит от сервера авторизации).
В перенаправленном запросе должны присутствовать в query string параметры code (код авторизации) и state (равен state из предыдущего перенаправления).
После этого веб-приложение отправляет на сервер авторизации запрос на получение токена доступа.
Для этого отправляется запрос по адресу POST getTokenUri() провайдера, используя client ID, client secret и code (в теле запроса в виде form) с заголовком Accept: application/json.
Полученный токен доступа используется для получения информации о пользователе.
Посылается запрос GET getUserInfoUri() провайдера, используя токен доступа.
полученный ответ передаётся в extractUserInfo(HttpResponse) провайдера, где для платформы заполняется информация о пользователе (логин/email/имена ролей/т.п.).
Полученный пользователь сохраняется в БД как внешний или обновляется, если уже существовал.
Генерируется внутренний токен для авторизации и посылается в ответе пользователю.
Внутренний токен действителен согласно обычным параметрам (org.unidata.mdm.core.security.token.ttl).
Отзыв токена доступа к серверу авторизации никак не подействует на внутренний токен.
С точки зрения frontend
Для старта аутентификации незалогиненный пользователь должен перейти на любую страницу приложения, в query-параметрах которой присутствуют sso=true, ssoType=oauth2 и oauth2ProviderId=<providerId>. Например:
http://localhost:8082/?sso=true&ssoType=oauth2&oauth2ProviderId=<providerId>#/dashboard
.На backend посылается запрос
GET universe-backend/api/v2/core/authentication/login
с query-параметрами sso=true, source=oauth2, oauth2=<providerId> и locale=<текущая локаль>.Пользователь перенаправляется на страницу логина внешнего сервиса.
Пользователь выполняет вход на стороннем сервисе.
Пользователь перенаправляется на callback-страницу приложения. В ней обязательно должны присутствовать query-параметры sso=true, ssoType=oauth2 и oauth2Callback=true:
Если также присутствуют параметры code и state, то на backend посылается
GET universe-backend/api/v2/core/authentication/login
c query-параметрами source=oauth2, code=<code> и state=<state>.
Если этот запрос вернул данные пользователя, то будет произведен вход и перенаправление на страницу из пункта 1.
Если этот запрос не пройдет, то будет отображена ошибка.
Если также присутствуют параметры error и error_description (например, если пользователь на пункте 4 запретил доступ к своим данным), то будет отображена ошибка.
Иначе пользователь будет перенаправлен на главную страницу приложения и будет отображена форма логина.
SDK
BE
Необходимые поля имплементации OAuth 2.0 провайдеров классы вынесены в SDK.
В этом SDK содержатся следующие классы:
com.universe.mdm.rest.v1.oauth2.type.OAuth2Provider
com.universe.mdm.rest.v1.oauth2.type.HttpResponse
com.universe.mdm.rest.v1.oauth2.type.OAuth2UserInfo
FE
Добавлен User-exit SsoAuthTypeManager, который позволяет добавлять поддержку различных типов SSO-авторизаций. User-exit активируется с присутствием query-параметра sso=true. Выбор между типами авторизации происходит исходя из присутствия и значения query-параметра ssoType.