Привет, Хабр! Меня зову Амир и я хотел бы сегодня поделиться с Вами своим опытом поднятия сервиса 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. Надеюсь было написано исчерпывающе, т.к. во время настройки всего я испытал много боли и может это кому то поможет.
Спасибо за прочтение.