Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Хочешь облегчить себе жизнь и сэкономить время?
Привет! Если ты так же как и я решил использовать keycloak для аутентификации и авторизации в своей микро-сервисной архитектуре, то я расскажу вам как правильно настроить сам keycloak, его рабочую среду а в конце мы подключим Active Directory к нашему приложению. Перед прочтением данного гайда прошу ознакомиться с keycloak по данной ссылке: https://habr.com/ru/company/southbridge/blog/654475/
Стек Технологии
Я Джавист от кожи до костей и именно поэтому все микро-сервисы у нас будут написаны в связке 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 и механизм наследования.
Благодаря 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/552494/. В моем случае конфигурация выглядит вот так:
<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 вашего компа":10389. Почему-то 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!