KeyCloak и микро-сервисы. Как облегчить жизнь программисту

от автора

Сын Маминой Подруги
Сын Маминой Подруги

Хочешь облегчить себе жизнь и сэкономить время?

Привет! Если ты так же как и я решил использовать 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 файл.

Дальше делайте все по этому алгоритму действий:

  1. Создайте новый realm для вашего проекта

  1. Создайте новый client для вашего нового realm

  1. Создайте несколько тестовых пользователей и навесьте на них несколько тестовых ролей. В моем случае это пользователи:

    1. username: arnur, roles: [«USER»]

    2. username: adal, roles: [«USER»]

  2. Теперь добавьте credentials вашего keycloak в ваш application.yml, чтобы в дальнейшем наше Spring Boot приложение могло генерировать токены и авторизировать пользователя:

keycloak:   auth-server-url: http://localhost:"ваш порт"/auth   resource: "название вашего клиента"   bearer-only: true   public-client: true   realm: "название вашего реалма"
  1. Теперь наступает время настройки вашего 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 и выберите авторизацию через токен. Вот самая дефолтная настройка:

Подключение 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *