Хочешь облегчить себе жизнь и сэкономить время?
Привет! Если ты так же как и я решил использовать keycloak для аутентификации и авторизации в своей микро‑сервисной архитектуре, то я расскажу вам как правильно настроить сам keycloak, его рабочую среду а в конце мы подключим Active Directory к нашему приложению. Перед прочтением данного гайда прошу ознакомиться с keycloak по данной ссылке.
Стек Технологии
Я Джавист от кожи до костей и именно поэтому все микро‑сервисы у нас будут написаны в связке Java 18 и Spring Boot. Сборник проекта у меня Maven, так как довольно простой в своем применении.
Информация по другим сервисам:
PostGreSql — база данных;
PgAdmin — GUI БД;
Сервис Аутентификации — KeyCloak V19;
Zipkin + Sleuth — технология трассировки запросов;
Kafka — Брокер Сообщений;
Eureka Server — регистрация наших микро‑сервисов.
Готовый docker‑compose файл:
services:
postgres:
container_name: postgres-gilgamesh
image: postgres
environment:
POSTGRES_USER: "здесь ваш username"
POSTGRES_PASSWORD: "здесь ваш password"
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: keycloak_db
volumes:
- postgres:/data/postgres
ports:
- "5432:5432"
networks:
- postgres
restart: unless-stopped
keycloak:
image: quay.io/keycloak/keycloak:legacy
platform: linux/arm64
environment:
DB_VENDOR: POSTGRES
DB_ADDR: postgres
DB_SCHEMA: public
DB_DATABASE: keycloak_db
DB_USER: "здесь ваш username от БД"
DB_PASSWORD: "здесь ваш password от БД"
KEYCLOAK_USER: "здесь ваш username"
KEYCLOAK_HOSTNAME: localhost
KEYCLOAK_PASSWORD: "здесь ваш пароль"
ports:
- 8082:8080
depends_on:
- postgres
networks:
- postgres
#Образ Готового LDAP если у вас нет тестового варианта
ldap:
image: rroemhild/test-openldap
ports:
- 10389:10389
- 10636:10636
networks:
- postgres
pgadmin:
container_name: pgadmin-gilgamesh
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin}
PGADMIN_CONFIG_SERVER_MODE: 'False'
volumes:
- pgadmin:/var/lib/pgadmin
ports:
- "5050:80"
networks:
- postgres
restart: unless-stopped
zipkin:
image: openzipkin/zipkin
container_name: zipkin-gilgamesh
ports:
- "9411:9411"
networks:
- spring
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- 22181:2181
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- 29092:29092
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
networks:
postgres:
driver: bridge
spring:
driver: bridge
volumes:
postgres:
pgadmin:
Настройка и конфигурация нашего parent pom.xml
Данный проект будет работать локально и все микро‑сервисы будут находиться в одной директории. Благодаря этому мы можем использовать особенность maven'a в которой мы можем указать parent файл, где будут лежать наши основные зависимости, которые нам не нужно будет прописывать по несколько раз.
В моем проекте будет несколько модулей, давайте сразу же их укажем в нашем parent pom.xml файле:
<modules>
<module>eureka-server</module>
<module>incidents</module>
<module>apigw</module>
<module>clients</module>
<module>kafka</module>
<module>authentication</module>
</modules>
У вас эти модули могут отличаться от моих, так как у вас может быть совершенно другая архитектура с другими сервисами.
Благодаря dependencyManagement в maven мы можем добавлять родительские зависимости в наш проект, благодаря которым, мы сможем использовать весь функционал его наследников. Выглядит это примерно вот так:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2.6.7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.6.7</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>20.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
"Дополнительно" Автоматическое обертывание сервиса в докер
Для автоматическое обертывание образов в докер я использую мой любимый JIB.
Jib создает оптимизированные образы Docker и OCI для ваших Java‑приложений без использования демона Docker — и без глубокого освоения лучших практик Docker. Он доступен в виде плагинов для Maven и Gradle, а также в виде библиотеки Java.
Основные цели JIB
Быстрота — Быстрое развертывание изменений. Jib разделяет ваше приложение на несколько слоев, отделяя зависимости от классов. Теперь вам не нужно ждать, пока Docker пересоздаст все ваше Java‑приложение — достаточно развернуть только те слои, которые изменились.
Воспроизводимость — при пересборке образа контейнера с тем же содержимым всегда создается один и тот же образ. Никогда больше не вызывайте ненужных обновлений.
Без демона — Сократите количество зависимостей от CLI. Создайте свой образ Docker из Maven или Gradle и отправьте его в любой реестр по вашему выбору. Больше не нужно писать Docker‑файлы и вызывать docker build/push.
Более подробно о JIB можно почитать вот здесь: https://habr.com/ru/post/552 494/. В моем случае конфигурация выглядит вот так:
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.7</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<from>
<image>eclipse-temurin:17</image>
<platforms>
<platform>
<architecture>arm64</architecture>
<os>linux</os>
</platform>
<platform>
<architecture>amd64</architecture>
<os>linux</os>
</platform>
</platforms>
</from>
<to>
<tags>
<tag>latest</tag>
</tags>
</to>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>
build
</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
Настройка простого сервиса
Давайте создадим простой сервис под названием Incident и добавим в него несколько роутов и засекюрим их при помощи keycloak.
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>gilgamesh_project</artifactId>
<groupId>com.project</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<artifactId>incidents</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>build-docker-image</id>
<build>
<plugins>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.project</groupId>
<artifactId>kafka</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
application.yml файл:
server:
port: 8080
spring:
application:
name: incidents
datasource:
password: password
url: jdbc:postgresql://localhost:5432/incident-service
username: postgres
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
show-sql: true
zipkin:
base-url: http://localhost:9411
kafka:
bootstrap-servers: localhost:29092
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
fetch-registry: true
register-with-eureka: true
Чтобы не засорять данный пост давайте предположим, что мы уже подключили брокер сообщении и у нас есть сервис, который обращается к другому сервису для получения информации о какой-либо сущности. Дальше мы расписываем наш тестовый контроллер:
@RestController
@RequestMapping("api/v1/incident/service")
@RequiredArgsConstructor
public class IncidentController {
private final IncidentService incidentService;
@PostMapping("/create")
public ResponseEntity<IncidentCreateDtoResponse> create(@Valid @RequestBody IncidentDtoRequest incidentDtoRequest) {
IncidentCreateDtoResponse incidentDtoResponse = IncidentCreateMapper.incidentCreateToDto(incidentService.create(incidentDtoRequest),new ArrayList<>());
return new ResponseEntity<>(incidentDtoResponse, HttpStatus.OK);
}
}
Замечательно, мы настроили наш сервис, теперь давайте подключим к нему keycloak.
Настройка и запуск KeyCloak
Откройте url по который вы указали в переменных keycloak в docker‑compose. Вас должна встретить следующая картина:
Если вы увидели данное окно, то поздравляю, вы успешно запустили keycloak. Теперь нажмите на admin console и авторизируйтесь в систему под данными, которые вы вводили в docker compose файл.
Дальше делайте все по этому алгоритму действий:
Создайте новый realm для вашего проекта.
Создайте новый client для вашего нового realm.
-
Создайте несколько тестовых пользователей и навесьте на них несколько тестовых ролей. В моем случае это пользователи:
username: arnur, roles: ["USER"]
username: adal, roles: ["USER"]
Теперь добавьте credentials вашего keycloak в ваш application.yml, чтобы в дальнейшем наше Spring Boot приложение могло генерировать токены и авторизировать пользователя:
keycloak:
auth-server-url: http://localhost:"ваш порт"/auth
resource: "название вашего клиента"
bearer-only: true
public-client: true
realm: "название вашего реалма"
Теперь наступает время настройки вашего spring security. Для этого при создании вашей конфигурации вы будете наследоваться от класса KeycloakWebSecurityConfigurerAdapter. Данный класс позволяет нам создавать сессии и конфигурировать защиту наших роутов. Пример кода:
@KeycloakConfiguration
@Import(KeycloakSpringBootConfigResolver.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(buildSessionRegistry());
}
@Bean
protected SessionRegistry buildSessionRegistry() {
return new SessionRegistryImpl();
}
@Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http.cors().and().csrf().disable();
http.authorizeRequests()
.antMatchers("/api/v1/incident/service/**").hasRole("USER");
http.authorizeRequests().anyRequest().permitAll();
}
}
Поздравляю теперь вы можете проходить авторизацию при помощи oauth2.0 токена, через keycloak. Чтобы проверить это используйте postman и выберите авторизацию через токен. Вот самая дефолтная настройка:
Access Token URL: http://»ваш урл»/auth/realms/»ваш реалм»/protocol/openid‑connect/token
Client ID: наименование вашего клиента
grant type: password credentials
username: username пользователя
password: пароль пользователя
scope: openid
Подключение LDAP к вашему проекту
Теперь начинается самый сок! KeyCloak позволяет нам использовать пользователей из AD для авторизации. Для примера я буду использовать готовый образ LDAP, который вы можете скачать по этой ссылке: https://hub.docker.com/r/rroemhild/test-openldap/. Делайте пул и разворачивайте в докере, мы живем в 21ом веке!
Чтобы добавить LDAP к вашему Keyсloak зайдите в раздел "User Federation" и выберите LDAP как ваш "User Provider" и укажите все согласно инструкции:
Console display name: какой душе угодно;
Connection URL: тут если вы развернули ldap через докер url будет такой: ldap://»ip вашего компа»:10 389. Почему‑то keycloak напрочь отказывается видеть ваш ldap через localhost даже если вы внедрите их в один docker‑network;
Bind Type: указывает BN вашего админа в случае тестового LDAP это: cn=admin,dc=planetexpress,dc=com
Bind credentials: указывает пароль вашего админа в случае тестового LDAP это: GoodNewsEveryone
EDIT MODE: советую ставить READ_ONLY
Users DN: указывает общую инфу вашего рядового пользователя в случае тестового LDAP это: ou=people,dc=planetexpress,dc=com
Username LDAP attribute: Имя атрибута LDAP, который отображается как имя пользователя Keycloak. Для нашего тестового LDAP это: uid
RDN LDAP attribute: Имя атрибута LDAP, который используется в качестве RDN (верхнего атрибута) типичного DN пользователя. Для нашего тестового LDAP это: uid
UUID LDAP attribute: Имя атрибута LDAP, который используется в качестве уникального идентификатора объекта (UUID) для объектов в LDAP. Для нашего тестового LDAP это: entryUUID
User object classes: Все значения атрибута LDAP objectClass для пользователей в LDAP, разделенные запятыми. Для нашего тестового LDAP это: inetOrgPerson, organizationalPerson, person, top
После этого нажмите save и в action выберите sync all users. После этого все юзеры из ldap должны синхронизироваться с вашим keycloak клиентом.
Дальше зайдите во вкладку Users и вы должны наблюдать примерно такую картину:
Теперь попробуйте задать некоторым пользователям созданную вами до этого роль (в моем случае это USER). И попробуйте сгенерировать токен. Если у вас все получилось, то поздравляю, вы успешно настроили KeyCloak как сервис аутентификации.
Итоги
Keycloak супер универсальный сервис авторизации и аутентификации. Он сильно экономит время разработки и позволяет настраивать систему авторизации как душе угодно. Keycloak так же крайне просто конфигурируется и запускается на любоей системе, а так же имеет открытый исходный код. А самое главное, он написан на JAVA!
Всем добра и позитива! Пользуйтесь Kcell и Activ!
Комментарии (12)
OMFGDom
00.00.0000 00:00+1Спасибо за туториал, давно искал подробные гайды без воды по KeyCloak, добавил в закладки, автору респект
wwalex101
00.00.0000 00:00+1если будет апи для фронта все же нужно будет в jwt делать преобразование со всеми вытекающими
ggo
00.00.0000 00:00Раз уж мы говорим про микросервисы, то для полноты картины, можно еще добавить OAuth API Gateway, например в лице Gogatekeeper.
LordBarov Автор
00.00.0000 00:00Приветствую! Такие компоненты как AG и брокера сообщений оставил за ширмой, чтобы уделить больше времени на KeyCloak. А так каждый разраб, который работал с микрашами, думаю знает об AG
Enrey
00.00.0000 00:00Не смотря на позитивные упоминания docker, imho слишком много ручной работы по конфигурированию keycloak/realm/ldap/...
Жду продолжения статьи с автоматическим конфигурированием keycloak при старте docker-контейнера https://github.com/adorsys/keycloak-config-cli :)
1q2w1q2w
00.00.0000 00:00Пара вопросов по статье:
1) в родительских зависимостях указан адаптер кейклок. Сам кейклок уже давно задепрекейтил все свои адаптеры, оставив по сути сам Auth server, а клиенты должны использоваться либо самописные либо сторонние, в случае с java это преимущественно spring auth2 client. Вообще была занятная история про адаптеры(ссылки легко можно найти): сначала разработчики spring решили выпилить auth server и объявили об этом сообществу. С рекомендацией переходить на сторонние решения вроде кейклок. Сообществу это не понравилось и они объявили протест, в связи с чем разработчики спринга выпустили свой новый authorization server(кажется в конце 2021 г). А уже в феврале 2022 кейклок обязал переходить с их адаптеров на сторонние клиенты. Да, знаю речь про разные части - Auth server и клиент, но по хронологии получается так что кейклок решил отдать поддержку клиентов на откуп сторонним разработчикам, а самим сосредоточиться на сервере. Т.е. если я все понял правильно клиентскую часть в самом приложении нужно переписать на spring auth2 client либо resource server если там будет только авторизация.
2) не хватает ссылочки на гитхаб, чтобы можно былаи понятна общая картина, сейчас отдельные части вроде eureka/sleuth упомянутые в статье оторваны от контекста(либо предполагается вторая часть с углубленным разбором cloud составляющей?)
3) просто вопрос: насколько сложно было бы заменить standalone keycloack на embedded в spring boot ? Возможность иметь сервер авторизации в обычном java приложении, поддерживаемом командой разработки видится заманчивой.
Спасибо.
1q2w1q2w
00.00.0000 00:00И поясните пожалуйста механизм работы кейклок с сессиями, упомянутыми в статье. Просто токены доступа(jwt или opaque) все таки никак не связаны с традиционными сессиями. Имеется ввиду что кейклок просто поддерживает состояние клиентов через выданные токены в виде сессий или это именно cookie с которыми клиенты делают запросы?
tia_ru
00.00.0000 00:00Внесу уточнения.
spring auth2 client (spring-security-oauth2-client) - для аутентификации к внешним сервисам по OAuth2.0/OIDC.
Keyclok Adapters - для авторизации входящих http-запросов по JWT-токену
Вместо Keyclok Adapters лучше использовать spring-security-oauth2-resource-server из состава Spring Security. Полагаю, что именно появление поддержки OAuth2.0/OIDC в Spring Security дало основание разработчикам Keycloak отказаться от развития Keyclok Adapters для Java.
tia_ru
00.00.0000 00:00У Keycloak 19 версии есть проблема -- работает только одна настройка "LDAP User Federation". Я не отслеживал, исправили ли её в последующих версиях. Для устранения этой проблемы мне в своё время пришлось написать плагин к Keycloak https://github.com/tia-ru/keycloak-md-ldap.
Плагин также позволяет интегрировать Keycloak с несколькими независимыми доменами AD (без взаимного доверия)
KirasiH
Норм статья