
Хочешь облегчить себе жизнь и сэкономить время?
Привет! Если ты так же как и я решил использовать 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 в 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’. Для Active directory это может быть ‘sAMAccountName’ или ‘cn’. Атрибут должен быть заполнен для всех записей пользователей LDAP, которые вы хотите импортировать из LDAP в Keycloak. Для нашего тестового LDAP это: uid
-
RDN LDAP attribute: Имя атрибута LDAP, который используется в качестве RDN (верхнего атрибута) типичного DN пользователя. Обычно оно совпадает с атрибутом Username LDAP, однако это не обязательно. Например, для Active directory принято использовать ‘cn’ в качестве атрибута RDN, когда атрибут имени пользователя может быть ‘sAMAccountName’. Для нашего тестового LDAP это: uid
-
UUID LDAP attribute: Имя атрибута LDAP, который используется в качестве уникального идентификатора объекта (UUID) для объектов в LDAP. Для многих поставщиков серверов LDAP это ‘entryUUID’; однако некоторые поставщики используют другие варианты. Например, для Active directory это должно быть ‘objectGUID’. Если ваш LDAP сервер не поддерживает понятие UUID, вы можете использовать любой другой атрибут, который должен быть уникальным среди пользователей LDAP в дереве. Например, ‘uid’ или ‘entryDN’. Для нашего тестового LDAP это: entryUUID
-
User object classes: Все значения атрибута LDAP objectClass для пользователей в LDAP, разделенные запятыми. Например: ‘inetOrgPerson, organizationalPerson’. Вновь созданные пользователи Keycloak будут записаны в LDAP со всеми этими классами объектов, а существующие записи пользователей LDAP будут найдены только в том случае, если они содержат все эти классы объектов. Для нашего тестового LDAP это: inetOrgPerson, organizationalPerson, person, top
После этого нажмите save и в action выберите sync all users. После этого все юзеры из ldap должны синхронизироваться с вашим keycloak клиентом.
Дальше зайдите во вкладку Users и вы должны наблюдать примерно такую картину:

Теперь попробуйте задать некоторым пользователям созданную вами до этого роль (в моем случае это USER). И попробуйте сгенерировать токен. Если у вас все получилось, то поздравляю, вы успешно настроили KeyCloak как сервис аутентификации.
ИТОГИ
Keycloak супер универсальный сервис авторизации и аутентификации. Он сильно экономит время разработки и позволяет настраивать систему авторизации как душе угодно. Keycloak так же крайне просто конфигурируется и запускается на любоей системе, а так же имеет открытый исходный код. А самое главное, он написан на JAVA!
Всем добра и позитива! Пользуйтесь Kcell и Activ!
ссылка на оригинал статьи https://habr.com/ru/post/720070/
Добавить комментарий