Работа SOWA с OAUTH2#
Platform V SOWA может быть установлена перед защищенным сервером и настроена на проверку пользовательских JWT токенов.
Ниже приведен алгоритм проверки:
Пользователь самостоятельно получает токен из IDP.
Пользователь обращается через SOWA на защищенный сервер, отправляя токен в заголовке apikey. Пример запроса:
curl -vk https://localhost:12345/back -X POST -d „request“ -H «apikey: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJaLWFLR0l1QU9JdWdpY09hYW1DMEMzQkRjNmNEekFJZThzN2VoVjFxLWFVIn0.eyJleHAiOjE3MjEwNTI5NTEsImlhdCI6MTcyMTA1MjY1MSwianRpIjoiMDNjYjBmZWEtY2VjOS00MTZjLThjZGUtYzMxZDJkOWI0MjVlIiwiaXNzIjoiaHR0cHM6Ly9wbGF0Zm9ybWF1dGgtaWZ0My5zYy5kZXYuc2J0L2F1dGgvcmVhbG1zL1BsYXRmb3JtQXV0aCIsImF1ZCI6WyJQbGF0Zm9ybUF1dGhaIiwiUGxhdGZvcm1VRlMiXSwic3ViIjoiYjNjY2Y5OTUtMTU3NS00MTQxLThmYzQtYmIwMTA5NTJlYmU4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiU293YUF1dGgiLCJzZXNzaW9uX3N0YXRlIjoiZWQwOWVkMTItMTZhYS00MmYwLWIxYzItMzM1ZDVkMDUyYmIxIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJwbGF0Zm9ybWF1dGhfdXNlciIsInVtYV9hdXRob3JpemF0aW9uIiwiZGVmYXVsdC1yb2xlcy1wbGF0Zm9ybWF1dGgiLCJzb3dhLWRldi10ZXN0LXJvbGUiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImxvZ2luIHByb2ZpbGUgdWZzX3Njb3BlIGVtYWlsIGVtcGxveWVlIiwic2lkIjoiZWQwOWVkMTItMTZhYS00MmYwLWIxYzItMzM1ZDVkMDUyYmIxIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiU293YSBUZXN0LVVzZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzb3dhLWRldi10ZXN0LXVzZXIiLCJnaXZlbl9uYW1lIjoiU293YSIsImZhbWlseV9uYW1lIjoiVGVzdC1Vc2VyIiwiZW1haWwiOiJzb3dhLWRldi10ZXN0LXVzZXJAbWFpbC5sb2NhbCJ9.HXUcu4sUTJy32d2JDjK7_iXkUzi0rBBdCR42xC7otHJxRLSKEhyLM116KKr9CB5MFTOi9x4ogoAazcF4dr4XuL7lOWOAfVYf_w3fjCiq6ZyKny1wej8LPRJzEcv92mwTtjsHW3Aah5NffGgOL_yb0q1i17CJbRw7qeJc9jg5v7xeDPx3uRYFhfgqT3uAuMRR2VwbCnKAczp8LDWf7hLnR76EMb4hD6uMrUdDilv0XxswI034cxOSbx2UpTNwNxO1-oOiZfYDpY9OIJrVJLNOi9N2muS9EWlVrz686AbhvGlGgQ3KeyMXCACsrM0KfcM32anZK74xgMvX-jr8o6L1ZBK9dFnidX6MlbBZg_ZN1GnUwaWqyzvQhX4WzMjvh1qTzj_IphuJIm2n7IWXkuRmpQ9fhYCgrELvghWzZN1zw7urdFAaKIrvnrzsoGaJc8Et1nl9gHaC0Uou9KEuDWktvfuuBSHIwbNNZsVcKWU8ez–WzUa6H5MBlt0BvxMobuLbSD4sAst6FPw8ImtIGj01LxdkDSw9-no_TBmqS5cArL7HtNmLALTvPndBBkvndWgOTXpFaJJeQO-A3gybhWmBbdy8vVRqxxaQCKy-uP7BkHxL9iMo1bovfCh37gkWPRmkWnB86zWX1opVQjxbI0JzusyAnepFbFgwlMb6yjgCUQ»
Далее на SOWA с помощью действия FileToVars подгружаются clientId & clientSecret из файла oauth (который берется из vault) и происходит генерация Basic заголовка с помощью действия Base64Encode.
С помощью действия HttpSender отправляется запрос с заголовком авторизации на сервис кэширования, который перенаправит запрос на IDP сервер для token introspection и закэширует полученный результат для конкретного токена на указанное время. Также при обращении на сервис кэширования предоставляется токен в виде заголовка с целью использования этого параметра в виде ключа для кэширования запросов. Результат будет закэширован на одну минуту в случае успешного ответа.
С помощью действий JsonPathExtractToVars и ConditionChecker проверяется поле active из json ответа. Если значение true - запрос идет на backend, если false - запрос блокируется.
Дополнительно проверяется наличие роли «sowa-dev-test-role» у токена.

Пример профиля с проверкой токена через introspect endpoint приведен в разделе.
Выпуск токена для пользователя посредством обращения на OAUTH2 сервер#
Благодаря гибкости цепочек также можно создать конфигурацию, которая будет самостоятельно обращаться в IDP и выпускать JWT токен за пользователя, в случае, если пользователь прислал свои Basic Authorization данные.
# case when apikey token empty and auth header exists
- actions:
# load client authentication data from file
- action: FileToVars
params:
rules:
# Имя переменной, в которую поместить изъятое значение
- var: $clj_client_id
# Ключ элемента
key: clientId
# Путь к файлу, их которого должно быть взято значение переменной. Переменные должны храниться в виде key=value (java properties)
filePath: /sowa/profile_storage/custom/oauth2/oauth
setwhenempty: true
- var: $clj_client_secret
# Ключ элемента
key: clientSecret
# Путь к файлу, их которого должно быть взято значение переменной. Переменные должны храниться в виде key=value (java properties)
filePath: /sowa/profile_storage/custom/oauth2/oauth
setwhenempty: true
conditions:
- {var: $http_apikey, operator: "=", val: ""}
- {var: $http_authorization, operator: "!=", val: ""}
# check that client authentication data was loaded
- action: ConditionChecker
params:
rules:
- var: $clj_client_id
operator: "!="
val: ""
- var: $clj_client_secret
operator: "!="
val: ""
- action: SetMessageFromVar
params:
var: http_authorization
conditions:
- {var: $http_apikey, operator: "=", val: ""}
- {var: $http_authorization, operator: "!=", val: ""}
actions:
# parse authorization header, split by 'space'
- action: DelimiterSplitter
params:
delimiter: " "
actions:
# for second part after splitting decode base64 and split by ':'
- action: Base64Decode
conditions:
- {var: $clj_message_index, operator: "=", val: "1"}
actions:
- action: DelimiterSplitter
params:
delimiter: ":"
actions:
# and first part will be login, second - password
- action: SetVarFromMessage
params:
variable: $clj_user_login
conditions:
- {var: $clj_message_index, operator: "=", val: "0"}
- action: SetVarFromMessage
params:
variable: $clj_user_password
conditions:
- {var: $clj_message_index, operator: "=", val: "1"}
actions:
- action: ConditionChecker
params:
rules:
- var: $clj_user_login
operator: "!="
val: ""
- var: $clj_user_password
operator: "!="
val: ""
# construct request to IDP to get token by user login/user password
- action: SetVar
params:
variables:
- name: clj_body
value: "client_id=$clj_client_id&client_secret=$clj_client_secret&grant_type=password&username=$clj_user_login&password=$clj_user_password"
- action: SetMessageFromVar
params:
var: clj_body
conditions:
- {var: $clj_body, operator: "!=", val: ""}
actions:
# send this generated request to IDP though service cache(we do not provide apikey, so our request will not be cached)
- action: HttpSender
params:
external_url: /auth/realms/PlatformAuth/protocol/openid-connect/token
service_id: request_to_oauth_with_cache
service_type: service_http_proxy
connect_timeout: 200s
headers:
- name: Content-Type
value: application/x-www-form-urlencoded
conditions:
- {var: $clj_message_size, operator: ">", val: "0"}
actions:
- action: JsonPathExtractToVars
params:
expressions:
- path: "$.access_token"
allowNotFound: false
var: clj_access_token_result
С помощью данных цепочек проставляется переменная clj_access_token_result и можно вернуть ее пользователю в ответе через Set-Cookie или в виде заголовка.
Также в SOWA есть возможность проверять сигнатуры и срок действия токенов через действие JwtVerifier.
Работа SOWA с OpenID#
Функциональность цепочек позволяет настроить на SOWA OpenID Authorization code flow.

Пример конфигурации приведен по ссылке.
Ниже рассмотрены отдельные этапы текущей диаграммы:
Шаг 1. Пользователь пытается вызвать защищенное API (или получить доступ к ресурсу) через SOWA без авторизационных данных (токена).
Шаг 2. SOWA генерирует необходимые параметры для запуска процесса аутентификации и перенаправляет пользователя постпроцессором на окно с аутентификационной формой с необходимостью ввода логина/пароля.
Осуществляется с помощью цепочек действий:
# если токен не найден, пользователь перенапрявляется при помощи постпроцессора на IDP для аутентификации
- actions:
- action: SetVar
params:
variables:
- name: clj_token_not_found
value: "true"
conditions:
# Здесь и далее - auth_token - сессионная кука SOWA
- {var: $cookie_auth_token, operator: "=", val: ""}
actions:
# генерируем nonce и проставляем результат в переменную clj_hmac_result
- action: HMac
params:
key: key
result_var: $clj_hmac_result
data: $request_id
base64_url_encode: true
Правило постпроцессора, отвечающее за редирект пользователя:
- condition:
- var: clj_token_not_found
operator: "="
val: "true"
- var: clj_processing_phase
operator: "="
val: "REQUEST"
redirectUrl: "https://platformauth-ift3.sc.dev.sbt/auth/realms/PlatformAuth/protocol/openid-connect/auth?client_id=$clj_client_id&response_type=code&redirect_uri=https%3A%2F%2F10.20.28.11%3A12345%2F_codexch&scope=openid&nonce=$clj_hmac_result"
priority: 2
status: 302
Шаг 3, 4, 5, 6. Пользователь аутентифицируется на стороне IDP и IDP после аутентификации пользователя предоставляет SOWA authorization code.
Шаг 7, 8. Используя полученный authorization code SOWA запрашивает у IDP пользовательские access/id/refresh токены:
# генерация тела запроса для получения access/id/refresh токенов
- action: SetVar
params:
variables:
- name: clj_body_to_exchange_keys
value: "grant_type=authorization_code&client_id=$clj_client_id&code=$arg_code&redirect_uri=https%3A%2F%2F10.20.28.11%3A12345%2F_codexch"
- action: SetMessageFromVar
params:
var: clj_body_to_exchange_keys
actions:
- action: HttpSender
params:
external_url: /auth/realms/PlatformAuth/protocol/openid-connect/token
service_id: request_to_openid_without_cache
service_type: service_http_proxy
connect_timeout: 200s
headers:
- name: Content-Type
value: "application/x-www-form-urlencoded"
- name: Authorization
value: "Basic $clj_client_basic_encoded"
actions:
# получаем ответ от IDP и сохраняем из ответа токена в память(ID токен обязательный, refresh и access токены - не обязательные)
- action: JsonPathExtractToVars
params:
expressions:
- path: $.id_token
allowNotFound: false
var: $clj_id_token
actions:
- action: VarsToSharedMap
params:
shared_store_name: id_token_by_auth_token
operation: REPLACE
rules:
- key: $request_id
var: $clj_id_token
...
Шаг 9, 10. Используя полученные токены пользователю проставляется session cookie ($request_id), по значению которой можно получать id/access/refresh токены из памяти и с данными токенами обращаться к API, или к IDP для обновления токенов в случае истечения их срока действия.
Шаг 11. Пользователь запрашивает оригинальную страницу уже с сессионной cookie.
Шаг 12. Получаем сессионную cookie, по ней получаем ID токен, валидируем.
Шаг 13. Проксируем запрос.
Шаг 14. Пользователь получает защищенный ресурс.
В дальнейшем, при обращении пользователя с session cookie, алгоритм действий SOWA следующий:
По session cookie берется из памяти ID токен. В случае, если ID токен не найден - запрос блокируется.
ID токен найден. Его актуальность проверяется при помощи вызова introspection endpoint IDP через service_cache (который будет кэшировать ответ от IDP).
2.1) ID токен активный - запрос пропускается.
2.2) ID токен неактивный - попытка обновить его при помощи refresh токена в IDP.
2.2.1) Обновить токен удалось - новые значения id/access/refresh токена сохраняются в памяти, запрос пропускается.
2.2.2) Обновить токен не удалось - запрос блокируется.