Введение

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

В этой статье мы настроим централизованную аутентификацию через сервис аутентификации на 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

https://openam.example.org/openam

Client secret

changeme

Confirm client secret

changeme

The audience for issued tokens

https://openam.example.org/openam

Настройка тестового пользователя

В консоли администратора 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 для статьи располагается по ссылке

Комментарии (0)