
Привет, Хабр! Меня зову Амир и я хотел бы сегодня поделиться с Вами своим опытом поднятия сервиса SSO на базе решения KeyCloak.
Вводные:
Требования со стороны бизнеса:
-
Для внутренних сервисов компании требуется единая точка входа с подключением пользователей из существующей Active Directory.
-
Требуется что бы пользователь мог иметь доступ к одному или нескольким сервисам (в каждом из сервисов имел одну или несколько ролей). Если доступ к тому или иному сервису отсутствует, сообщать ему об этом.
Требования взаимодействия с КС
-
KC должен работать по https.
-
На стороне фронта будет использоваться пакет от KC https://www.npmjs.com/package/keycloak-js.
-
Возможность отправлять события в Kafka
Требования развертки
-
KC должен быть развернут в Docker с помощью Docker compose
Версия KC: 25.0.2, так же проверял на версии 26.0.0 (тоже норм)
Ну вот с вводными разобрались, теперь приступим к реализации.
Build and deploy
Dockerfile
FROM keycloak/keycloak:25.0.2 COPY keycloak-kafka-1.1.5.jar /opt/keycloak/providers/ ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
Для подключения Kafka к CK используем keycloak-kafka-1.1.5
Dokcer-compose.yaml
version: "3.9" services: keycloak: image: my_docker_hub/keycloak:latest volumes: - ./themes:/opt/keycloak/themes - ./cert/cert.jks:/etc/x509/https/truststore.jks container_name: keycloak ports: - "8443:8443" env_file: ./.env command: start depends_on: keycloak-postgres: condition: service_healthy healthcheck: test: [ "CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/9000;echo -e 'GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n' >&3;if [ $? -eq 0 ]; then echo 'Healthcheck Successful';exit 0;else echo 'Healthcheck Failed';exit 1;fi;", ] start_period: 10s interval: 30s retries: 3 timeout: 5s keycloak-postgres: container_name: keycloak-postgres image: postgres volumes: - ./db/data:/var/lib/postgresql/data env_file: ./.env healthcheck: test: pg_isready -d postgres interval: 10s timeout: 5s retries: 3 start_period: 5s
.env
KC_FEATURES: preview KC_HEALTH_ENABLED: true KC_METRICS_ENABLED: true KC_HOSTNAME: host_keycloak KC_HTTPS_PORT: 8443 KC_HTTPS_KEY_STORE_PASSWORD=STORE_PASSWORD KC_HTTPS_KEY_STORE_FILE=/etc/x509/https/truststore.jks KC_PROXY_HEADERS: xforwarded KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin KAFKA_TOPIC: user.event.user KAFKA_ADMIN_TOPIC: user.event.admin KAFKA_CLIENT_ID: keycloak KAFKA_BOOTSTRAP_SERVERS: BOOTSTRAP_SERVERS KAFKA_EVENTS: LOGIN,LOGOUT KC_DB: postgres KC_DB_URL: jdbc:postgresql://keycloak-postgres:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: keycloak KC_DB_SCHEMA: public POSTGRES_DB: keycloak PGUSER: keycloak POSTGRES_USER : keycloak POSTGRES_PASSWORD : keycloak PGPASSWORD: password
Процесс сборки и развертывания опускаю, т.к. там ничего интересного нет.
Настраиваем HTTPS (В моем случае у меня есть root cert)
На этом моменте я прям залип знатно, т.к. в интернетах приводятся вагон вариаций исполнения, но к сожалению много устаревших либо не подходящих по типу сертификата
-
Копируем root сертификат в /opt/keycloak/cert
-
Выполняем команды
keytool -importkeystore -srckeystore cert.pfx -srcstoretype pkcs12 -destkeystore ./cert.jks -deststoretype pkcs12 cd /opt/keycloak/cert
Указываем пароль от сертификата cert.pfx и назначаем пароль для keystore
-
В docker-compose прокидываем cert.jks
volumes: - ./cert/cert.jks:/etc/x509/https/truststore.jks
-
В файле .env
Прописываем следующие переменные
KC_HOSTNAME: host_keycloak KC_HTTPS_PORT: 8443 KC_HTTPS_KEY_STORE_PASSWORD=STORE_PASSWORD KC_HTTPS_KEY_STORE_FILE=/etc/x509/https/truststore.jks KC_PROXY_HEADERS: xforwarded
После развертывания через Docker compose, можем открывать KC.
Поздравляю! До этого момента я добирался долго )
https://host_name:8443/

Заходим под admin / admin
Подключаем AD
Создаем свой Realm
Идем в User federation и создаем Ldap providers

Далее действовал по описанию в статье
https://habr.com/ru/companies/swordfish_security/articles/533264/
Ребятам и Swordfish Security огромное спасибо за статью
По обычаю, принимаясь за задачу по KC хотел уже потратить день другой на блогах и форумах в поисках решения/гайда, как тут уже все написано 🙂
Подключаем Kafka
Заходим в Realm settings, переходим в Events, в селекте выбираем kafka

Теперь все события произведенные в админе будут отправляться в топик указанный в .env admin, а пользовательские события login/logout в топик user
Настройка Client
Создаем Client


После создания в настройках обязательно указываем разрешенные URL откуда можем обращаться и т.д.

Создаем client scope audience, что бы добавить аудиенцию клиента в токен.


Переходим на вкладку Mappers, жмём на Configure a new mapper


Name — указываете как удобно
Included Client Audience — выбираем нашего клиента
Сохраняем.
Добавляем наш scope нашему клиенту
Переходим в меню Clients — вкладка Client scopes — Add client scope

Выбираем наш scope и добавляем с признаком Default
Создание роли доступа для нашего client

Меню — Realm roles — Create role

Создание группы пользователей для доступа в наш client

Меню — Groups — Create group
Название я предпочитаю задавать для групп доступа = client id

Связываем роль доступа с нашей группой на вкладке Role mapping

Создание Flow для аутентификации через браузер для нашего клиента
Меню — authentication — Flows — дублируем browser


Далее формируем следующую структуру
Странно, но не думал что ситуация которую мне нужно было разрешить, настолько редкая и по этой тепе в интернете было совсем мало инфы (нуууу очень мало). Решение нашел на stackoverflow, которое было представлено в виде скрина, которое в итоге было модифицировано 🙂
Flow - Required: { name: Login: <Название клиента> } Step - Alternative: Cookie Step - Alternative: Identity Provider Redirector config Flow - Alternative: { name: gated browser form: <Название клиента> } Step - Required: Username Form Flow - Conditional: { name: gated browser form - Conditional OTP Form config: <Название клиента> } Condition - Required: Condition - user configured Step - Required: OTP Form Flow: { name: RBA - Conditiona: <Название клиента> } Condition - Required: Condition - user role { Alias: user role <Название клиента>, User role: Выбираем роль созданную для нашего Client, Negate output: On } Step - Required: Deny access { Alias: Deny access config <Название клиента>, Error message: Доступ в приложение <Название приложения> запрещен }



Далее, указываем наш созданный Flow как основной для Client


Если сейчас мы попробуем пройти авторизацию в KC через адаптер keycloak-js получим следующий результат

Добавление пользователя в группу доступа к my-client
Меню — members — Add member — выбираем пользователя из локальной базы или импортированного из AD

Далее пробуем пройти авторизацию через адаптер keycloak-js, получим результат «Успешная авторизация» с получением токена и редиректа на страницу указанную в настройках Client.
Итог
В этой мы рассмотрели реализацию авторизации в нашем приложении через популярное SSO ПО KeyCloak. Надеюсь было написано исчерпывающе, т.к. во время настройки всего я испытал много боли и может это кому то поможет.
Спасибо за прочтение.
ссылка на оригинал статьи https://habr.com/ru/articles/856532/
Добавить комментарий