Введение
Если в организации множество приложений и сервисов, то нет необходимости разрабатывать аутентификацию и авторизацию для каждого сервиса отдельно. Оптимальным подходом является использование централизованного сервиса аутентификации совместно со шлюзом авторизации, который и определяет политики доступа к приложениям.
В этой статье мы настроим централизованную аутентификацию через сервис аутентификации на Open Access Manager (OpenAM) и настроим доступ к приложению через шлюз авторизации Open Identity Gateway (OpenIG), который будет использовать сессию аутентификации OpenAM. В качестве защищаемого приложения будем использовать приложение, разработанное с использованием Spring Boot и Spring Security. Исходный код приложения располагается на GitHub.
Для демонстрационных целей все сервисы запустим в Docker контейнерах.
Процесс аутентификации и авторизации к защищаемому ресурсу показан на рисунке:

Запуск демонстрационного приложения
Создайте файл docker-compose.yaml и добавьте в него демонстрационное приложение. Пробросьте порт 8081 для проверки работоспособности.
services: spring-service: container_name: spring-service image: openidentityplatform/spring-security-openam-example restart: always ports: - "8081:8081" environment: JAVA_OPTS: -Dspring.profiles.active=jwt networks: openam_network: aliases: - spring-service networks: openam_network: driver: bridge
После того, как приложение запущено, проверим к доступ к API, для которого требуется добавить аутентификацию.
curl http://localhost:8081/api/protected-jwt | json_pp % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 105 0 105 0 0 12684 0 --:--:-- --:--:-- --:--:-- 13125 { "error" : "Unauthorized", "path" : "/protected-jwt", "status" : 401, "timestamp" : "2024-04-16T06:01:25.331+00:00" }
Для успешной аутентификации, в API нужно передать валидный JWT в HTTP заголовке Authorization. За это и будет отвечать связка OpenAM + OpenIG. Потушите пока тестовый сервис командной docker compose down
Настройка OpenAM
Сначала настроим сервис аутентификации OpenAM. Он будет отвечать за аутентификацию и конвертацию токена аутентификации в JWT (об этом ниже).
Добавьте имена хостов OpenAM и OpenIG в файл hosts, например 127.0.0.1 openam.example.org openig.example.org .
В Windows системах файл hosts находится по адресу C:\\Windows\\System32\\drivers\\etc\\hosts , в Linux и Mac находится по адресу /etc/hosts .
Добавьте в файл docker-compose.yaml сервис OpenAM:
... openam: image: openidentityplatform/openam:latest container_name: openam ports: - "8080:8080" networks: openam_network: aliases: - openam.example.org ...
Запустите OpenAM командой docker compose up openam. После того, как OpenAM запущен, сконфигурируйте его командой:
docker exec -w '/usr/openam/ssoconfiguratortools' openam bash -c \ 'echo "ACCEPT_LICENSES=true SERVER_URL=http://openam.example.org:8080 DEPLOYMENT_URI=/$OPENAM_PATH BASE_DIR=$OPENAM_DATA_DIR locale=en_US PLATFORM_LOCALE=en_US AM_ENC_KEY= ADMIN_PWD=passw0rd AMLDAPUSERPASSWD=p@passw0rd COOKIE_DOMAIN=example.org ACCEPT_LICENSES=true DATA_STORE=embedded DIRECTORY_SSL=SIMPLE DIRECTORY_SERVER=openam.example.org DIRECTORY_PORT=50389 DIRECTORY_ADMIN_PORT=4444 DIRECTORY_JMX_PORT=1689 ROOT_SUFFIX=dc=openam,dc=example,dc=org DS_DIRMGRDN=cn=Directory Manager DS_DIRMGRPASSWD=passw0rd" > conf.file && java -jar openam-configurator-tool*.jar --file conf.file'
И дождитесь завершения выполнения.
Настройка STS
STS (Security Token Service) - сервис конвертации токена OpenAM в JWT. После аутентификации OpenAM возвращает токен аутентификации, который является случайно сгенерированной последовательностью символов. Сервис STS отвечает за конвертацию токена аутентификации в JWT, который и содержит информацию об аутентифицированном пользователе. Этот сервис и будет использовать OpenIG.
Для настройки STS зайдите в консоль администратора по ссылке
http://openam.example.org:8080/openam/XUI/#login/
В поле логин введите значение amadmin, в поле пароль введите значение из параметра ADMIN_PWD команды установки, в данном случае passw0rd

Откройте корневой realm, в левом меню нажмите пункт STS и создайте новый инстанс со следующими параметрами:
Setting | Value |
|---|---|
Supported Token Transforms | OPENAM->OPENIDCONNECT;don't invalidate interim OpenAM session |
Deployment Url Element | jwt |
The id of the OpenID Connect Token Provider | |
Client secret | changeme |
Confirm client secret | changeme |
The audience for issued tokens |
Настройка тестового пользователя
В консоли администратора OpenAM перейдите в корневой realm, в меню слева выберите пункт Subjects. Задайте пароль для пользователя demo. Для этого выберите его в списке пользователей, и нажмите ссылку Edit в пункте Password. Введите и сохраните новый пароль. После настройки выйдите из консоли администратора.
Настройка OpenIG
OpenIG отвечает за политики доступа к внутренним сервисам. Он конвертирует токен аутентификации в JWT в сервисе OpenAM и, при успешной авторизации предает передает во внутренние сервисы в заголовке Authorization.
Создайте папку openig-config и добавьте в нее 2 файла: admin.json
{ "prefix": "openig", "mode": "PRODUCTION" }
и config.json
{ "heap": [], "handler": { "type": "Chain", "config": { "filters": [], "handler": { "type": "Router", "name": "_router", "capture": "all" } } } }
Теперь добавим маршрут, который будет перенаправлять запросы пользователя в демонстрационное приложение, обогащая запрос заголовком Authorization с JWT токеном, полученным из OpenAM.
Создайте папку openig-config/routes/ и добавьте в нее файл 10-protected.json.
{ "name": "${matches(request.uri.path, '^/api/protected-jwt') || matches(request.uri.path, '^/protected-jwt')}", "condition": "${matches(request.uri.path, '^/api/protected-jwt') || matches(request.uri.path, '^/protected-jwt')}", "monitor": true, "timer": true, "handler": { "type": "Chain", "config": { "filters": [ { "type": "ConditionalFilter", "config": { "condition": "${empty contexts.sts.issuedToken and not empty request.cookies['iPlanetDirectoryPro'][0].value}", "delegate": { "type": "TokenTransformationFilter", "config": { "openamUri": "${system['openam']}", "realm": "/", "instance": "jwt", "from": "OPENAM", "to": "OPENIDCONNECT", "idToken": "${request.cookies['iPlanetDirectoryPro'][0].value}" } } } }, { "type": "ConditionalFilter", "config": { "condition": "${not empty contexts.sts.issuedToken}", "delegate": { "type": "HeaderFilter", "config": { "messageType": "REQUEST", "remove": [ "Authorization", "JWT" ], "add": { "Authorization": [ "Bearer ${contexts.sts.issuedToken}" ] } } } } }, { "type": "ConditionEnforcementFilter", "config": { "condition": "${not empty contexts.sts.issuedToken}", "failureHandler": { "type": "StaticResponseHandler", "config": { "status": 302, "reason": "Found", "headers": { "Content-Type": [ "application/json" ], "Location": [ "${system['openam']}/UI/Login?org=/&goto=${urlEncode(contexts.router.originalUri)}" ] }, "entity": "{ \\"Redirect\\": \\"${system['openam']}/UI/Login?org=/&goto=${urlEncode(contexts.router.originalUri)}\\"}" } } } } ], "handler": "EndpointHandler" } }, "heap": [ { "name": "EndpointHandler", "type": "DispatchHandler", "config": { "bindings": [ { "handler": "ClientHandler", "capture": "all", "baseURI": "${matchingGroups(system['spring-service'],\\"((http|https):\\/\\/(.[^\\/]*))\\")[1]}" } ] } } ] }
Этот маршрут авторизует две конечные точки: API - /api/protected-jwt и UI - /protected-jwt.
Добавьте сервис OpenIG в docker-compose.yaml файл и уберите маппинг портов из сервиса spring-service. Теперь этот сервис доступен только через OpenIG.
services: openam: image: openidentityplatform/openam:latest container_name: openam ports: - "8080:8080" networks: openam_network: aliases: - openam.example.org openig: image: openidentityplatform/openig:latest container_name: openig volumes: - ./openig-config:/usr/local/openig-config:ro environment: CATALINA_OPTS: -Dopenig.base=/usr/local/openig-config -Dspring-service=http://spring-service:8081 -Dopenam=http://openam.example.org:8080/openam ports: - "8081:8080" networks: openam_network: aliases: - openig.example.org spring-service: container_name: spring-service image: openidentityplatform/spring-security-openam-example restart: always # ports: # - "8081:8081" environment: JAVA_OPTS: -Dspring.profiles.active=jwt networks: openam_network: aliases: - spring-service networks: openam_network: driver: bridge
Запустите OpenIG и тестовое приложение командой docker compose up openig spring-service
Проверка решения
Проверка авторизации API
Получим токен аутентификации OpenAM для пользователя demo:
curl -X POST -H "X-OpenAM-Username: demo" -H "X-OpenAM-Password: passw0rd" \\ -H "Content-Type: application/json" -H "Accept-API-Version: resource=2.1" \\ <http://openam.example.org:8080/openam/json/realms/root/authenticate> | json_pp % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 159 100 159 0 0 4197 0 --:--:-- --:--:-- --:--:-- 4297 { "realm" : "/", "successUrl" : "/openam/console", "tokenId" : "AQIC5wM2LY4Sfcze3DbBXVSXggTyZNpGfwOoFPLnHwmqLG0.*AAJTSQACMDEAAlNLABM2MTY1Mjg2MzI5Mzc4ODM0MzQ5AAJTMQAA*" }
Вызовем с полученным токеном endpoint /api/protected-jwt
curl --cookie "iPlanetDirectoryPro=AQIC5wM2LY4Sfcze3DbBXVSXggTyZNpGfwOoFPLnHwmqLG0.*AAJTSQACMDEAAlNLABM2MTY1Mjg2MzI5Mzc4ODM0MzQ5AAJTMQAA*" \\ "<http://openig.example.org:8081/api/protected-jwt>" | json_pp % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 30 0 30 0 0 1071 0 --:--:-- --:--:-- --:--:-- 1111 { "method" : "JWT", "user" : "demo" }
OpenIG через OpenAM сконвертировал переданный в cookie iPlanetDirectoryPro токен аутентификации в JWT и передал этот JWT в демонстрационное приложение spring-service. Демонстрационное приложение получило из JWT информацию о пользователе и вернуло успешный ответ.
Проверка авторизации UI
Выйдите из консоли администратора или откройте браузер в режиме “Инкогнито”
Откройте в браузере URL http://openig.example.org:8081/protected-jwt . OpenIG не найдет cookie с токеном аутентификации и перенаправит браузер на аутентификацию OpenAM. Введите логин demo , пароль и нажмите кнопку Log In.

После успешной аутентификации шлюз перенаправит запрос к Spring Boot приложение. Получит из http запроса cookie с токеном аутентификации, сконвертирует токен в OpenAM в сервисе STS в JWT и передаст полученный JWT в демонстрационное приложение. Демонстрационное приложение проверит JWT и вернет успешный ответ.

Исходный код демонстрационного приложения располагается по ссылке
Исходный код конфигурации docker conpose и маршрутов OpenIG для статьи располагается по ссылке
