Введение

Данная статья является инструкцией для новичков, которые хотели бы использовать Keycloak в своих проектах в качестве безопасности. В статье будет рассказано:

  1. Что такое Keycloak и для чего он нужен.

  2. Как запустить Keycloak.

  3. Как создать свой первый realm.

  4. Как настроить Keycloak.

  5. Как интегрировать Keycloak в свое приложение на Spring.

В данной статье не будет подробного изучения данного сервера. Я разберу основные моменты, которые нужно знать, чтобы начать использование.

Keycloak

Для начала разберемся для чего нужен keycloak.

Keycloak — это система управления доступом с открытым исходным кодом, которая позволяет добавлять аутентификацию и авторизацию в приложения и сервисы. Она предоставляет функции единого входа (SSO), а также поддерживает различные протоколы аутентификации, такие как OpenID Connect, OAuth 2.0 и SAML.

Основные возможности Keycloak:

  1. Единый вход (SSO): позволяет пользователям входить в систему один раз и получать доступ ко всем связанным приложениям без повторной аутентификации.

  2. Социальная аутентификация: поддержка интеграции с социальными сетями, такими как Google, Facebook, Twitter, для аутентификации пользователей.

  3. Централизованное управление пользователями: администраторы могут управлять пользователями, ролями и разрешениями из единого интерфейса.

  4. Поддержка различных протоколов: как упоминалось ранее, Keycloak поддерживает множество стандартных протоколов аутентификации и авторизации.

  5. Настраиваемая и расширяемая: возможности платформы можно расширить за счет использования различных плагинов и адаптеров.

  6. Интеграция с LDAP и Active Directory: возможность интеграции с существующими системами управления пользователями.

Keycloak часто используется в больших организациях и проектах, требующих надежной и масштабируемой системы управления доступом.

Запуск Keycloak

Для начала создадим наш проект Spring, для этого можно использовать открытый для всех spring initializer - https://start.spring.io/ или же, если есть IntelliJ IDEA Ultimate, то можно создать проект Spring прям в ней.

Добавим нужные зависимости и создадим проект:

Spring Initializer и нужные зависимости
Spring Initializer и нужные зависимости

После того, как наш проект сформируется, у нас появится файл pom.xml, в котором будут прописаны все наши зависимости:

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>OauthTest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>OauthTest</name>
    <description>OauthTest</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Для дальнейшего использования Keycloak нам нужно прописать еще одну зависимость. Для того чтобы найти нужную зависимость и актуальную версию, можно использовать сайт - https://mvnrepository.com/

 <!-- https://mvnrepository.com/artifact/org.keycloak/keycloak-admin-client -->
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-admin-client</artifactId>
            <version>26.0.2</version>
        </dependency>

Чтобы запустить keycloak, я буду использовать Docker. Для этого создадим в корневой папке нашего проекта файл docker-compose.yml и пропишем в нем загрузку образов и создание контейнеров. Также я буду использовать вместо базы данных H2, которая установлена по-умолчанию в keycloak, базу данных posgreSQL, которую также запустил в Docker и подключил к keycloak.

docker-compose.yml
services:
  postgres:
    container_name: Postgres
    image: postgres
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: root
      POSTGRES_DB: keycloak
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - 5435:5432
    networks:
      - keycloak-network
    restart: unless-stopped

  keycloak:
    container_name: Keycloak
    image: quay.io/keycloak/keycloak:latest
    ports:
      - 9090:8080
    environment:
      DB_VENDOR: POSTGRES
      DB_ADDR: postgres
      DB_DATABASE: keycloak
      DB_SCHEMA: public
      DB_USER: root
      DB_PASSWORD: root
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    networks:
      - keycloak-network
    depends_on:
      - postgres
    command:
      - "start-dev"
networks:
  keycloak-network:
    driver: bridge

volumes:
  postgres_data:
    driver: local
  keycloak:
    driver: local

Наш keycloak будет работать на порту 9090. После запуска контейнера мы можем перейти на наш сервер по ссылке http://localhost:9090. Для того, чтобы войти в настройки keycloak, нам нужно вести данные, которые мы указали при создании контейнера.

KEYCLOAK_ADMIN: admin

KEYCLOAK_ADMIN_PASSWORD: admin

Вход в Keycloak
Вход в Keycloak

Keycloak запущен, можно приступать к настройкам.

Настройка Keycloak

После входа, нам нужно создать наш realm. Realm (область) — это пространство, которое управляет набором пользователей, учетных данных и ролей. Реалмы позволяют изолировать данные и настройки, чтобы различные приложения и пользователи могли существовать в одном экземпляре Keycloak без пересечения данных.

Создание нового realm
Создание нового realm
Создание нового realm
Даём название нашему realm
Даём название нашему realm

После создания нашего realm нам нужно создать нового client. Для этого переходим в раздел Clients и нажимаем кнопку Create client.

Создание своего Client
Созданием нового client
Созданием нового client
  1. Даём имя нашему client.

Даем название client
Даем название client
  1. Устанавливаем в Client authentication флаг на On.

Устанавливаем флаг On
Устанавливаем флаг On
  1. Прописываем наши URL.

Прописываем наши URL
Прописываем наши URL

Данный URL будет использовать наш бэк. В дальнейшем мы это все пропишем.

После того как мы создали нашего client, создадим нового пользователя (User). Для этого перейдем во вкладку Users. Здесь нам нужно создать пользователя, указав логин и пароль.

Создание нового пользователя
Создание нового пользователя
Создание нового пользователя

Пропишем данные в полях.

Заполнение данных
Заполнение данных

После создания, данный пользователь не будет иметь пароля, для это установим его во вкладке Credentials.

Добавление пароля
Добавление пароля
Прописывание пароля
Прописывание пароля

Теперь у нас есть пользователь в базе данных. Давайте проверим можно ли войти под этими данными. Для этого создадим файл scratch.http и пропишем следующее:

POST http://localhost:9090/realms/OauthTests/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

client_id=myclient&client_secret=SECRET&username=kudzip&password=test&grant_type=password

client_id - это название, созданного нами client.

client_secret - это секретный ключ нашего client, его можно скопировать следуя инструкциям в пункте Копирование client secret. Данный ключ нужно вставить вместо слова SECRET.

Копирование client secret
Поиск секретного ключа нашего client
Поиск секретного ключа нашего client
Копирование client secret
Копирование client secret

username и password - это username и password, созданного нами пользователя.

После отправления запроса нам в ответ генерируется access_token и refresh_token:

Отправление запроса
Отправление запроса

Данный запрос можно выполнить в postman.

Как видно из изображения, все работает исправно. Проверим данный токен на сайте - JWT.IO.

Проверка JWT
Проверка JWT

Данный токен несет в себе основную информацию о пользователе. В поле "полезная нагрузка" можно увидеть все нужные данные. (email, role, name, family, username и тд.)

Во вкладке Clients можно посмотреть какую информацию у нас будет хранить JWT. Давайте на это посмотрим. Для этого нужно перейти во вкладку Clients и нажать на созданный нами myclient.

Просмотр токена
Просмотр нашего Client
Просмотр нашего Client
Просмотр access token
Просмотр access token

В поле Users нужно выбрать интересующего нас пользователя.

{
  "exp": 1733502948,
  "iat": 1733502648,
  "jti": "85cd30db-8b48-49a0-bf91-45a8e649e20a",
  "iss": "http://localhost:9090/realms/OauthTests",
  "aud": "account",
  "sub": "4b65107a-99f2-49dc-a161-e12bf2fdc1f3",
  "typ": "Bearer",
  "azp": "myclient",
  "sid": "70a67a84-f849-4b43-a3f8-b5d42b1f4daa",
  "acr": "1",
  "allowed-origins": [
    "http://localhost:8083"
  ],
  "realm_access": {
    "roles": [
      "default-roles-oauthtests",
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid profile email",
  "email_verified": true,
  "name": "ivan Storozhev",
  "spring_sec_roles": [
    "default-roles-oauthtests",
    "offline_access",
    "uma_authorization"
  ],
  "preferred_username": "kudzip",
  "given_name": "ivan",
  "family_name": "Storozhev",
  "email": "test@test.ru"
}

Такую же информацию мы получили, когда расшифровали наш JWT на сайте.

Для чего нужны Access и Refresh токены?

Они служат для управления доступом к ресурсам и поддержания сеансов пользователей.

Access-токен

  1. Предоставление доступа: Access-токен предоставляет клиентскому приложению права доступа к защищённым ресурсам от имени пользователя или сервиса. Он содержит информацию о пользователе и правах доступа.

  2. Краткосрочный срок действия: Обычно имеют короткий срок действия (несколько минут или часов) для снижения риска компрометации.

  3. Использование в API-запросах: Access-токены передаются в заголовках запросов к API, чтобы сервер мог подтвердить, что запрос авторизован.

  4. JSON Web Tokens (JWT): Часто реализуются в формате JWT, что позволяет серверам быстро проверять подпись и данные токена без дополнительного обращения к серверу авторизации.

Refresh-токен

  1. Обновление access-токена: Refresh-токен используется для получения нового access-токена, когда старый истекает, без необходимости повторной аутентификации пользователя.

  2. Долгосрочный срок действия: Имеют более длительный срок действия по сравнению с access-токенами (дни или недели), что позволяет поддерживать долгосрочные сеансы.

  3. Хранение и безопасность: Должны храниться надежно, так как компрометация refresh-токена может позволить злоумышленнику получать новые access-токены без ведома пользователя.

  4. Не передаются с каждым запросом: В отличие от access-токенов, refresh-токены не используются в обычных API-запросах, а только для обновления access-токенов.

Вместе эти токены обеспечивают баланс между безопасностью и удобством, позволяя пользователям оставаться авторизованными в течение длительного времени, но минимизируя риски, связанные с компрометацией токенов.


Иногда нужно добавить определенную роль, чтобы доступ к странице был только для ограниченных пользователей. Такое можно сделать во вкладке Realm roles. Я добавлю роль менеджера "ROLE_MANAGER".

Создание новой роли
Создание новой роли
Создание новой роли
Даём название нашей роли
Даём название нашей роли

Теперь у нас есть отдельная роль для менеджеров. В конфиге spring security мы пропишем безопасность с учетом ролей.

Подключение авторизации и регистрации через сторонние API. Технология Oauth достаточно удобна и популярна, ее используют сейчас практически везде и с помощью Keycloak, можно подключить авторизацию и регистрацию через Google, GitHub и др. сервисы. Я воспользуюсь API только Google и GitHub. Давайте посмотрим как это сделать.

Google

Чтобы подключить Google нужно перейти на сайт с API. Здесь нам нужно создать новый Oauth client ID.

Создание нового Oauth client ID
Создание нового Oauth client ID

Откроется окно по созданию client. Первым делом нам нужно выбрать тип приложения (я выбрал Web application). Далее нужно дать имя нашему client. После нужно указать Redirect URI. Как получить Redirect URI описано в пункте Получение Redirect URI.

Заполнение полей
Заполнение полей
Получение Redirect URI
Поиск Redirect URI 
Поиск Redirect URI 

После нажатия на "Add provider" будет выбор из нескольких провайдеров, нам нужно выбрать Google.

Копирование Redirect URI
Копирование Redirect URI

Когда мы создали наш Oauth client ID, нужно зайти в него, чтобы узнать Client ID и Client secret.

Переход к нашему Client
Переход к нашему Client
Узнаем наши Client ID и Client secret
Узнаем наши Client ID и Client secret

Нам нужно скопировать эти 2 поля и перейти к Keycloak. Здесь нам нужно во вкладке, где мы копировали Redirect URI вставить недостающие поля.

Client ID - скопированный Client ID с API Google.

Client Secret - скопированный Client Secret с API Google.

Заполнение полей
Заполнение полей

GitHub

Чтобы подключить GitHub нужно перейти на сайт Developer settings. Здесь нам нужно создать new Oauth app.

Создание new Oauth app
Создание new Oauth app

Далее заполняем поля.

Заполнение полей
Заполнение полей
Получение Redirect URI
Поиск Redirect URI 
Поиск Redirect URI 

После нажатия на "Add provider" будет выбор из нескольких провайдеров, нам нужно выбрать GitHub.

Копирование Redirect URI
Копирование Redirect URI

Заходим в созданный нами Oauth app, чтобы узнать Client ID и Client secret.

Узнаем наши Client ID и Client secret
Узнаем наши Client ID и Client secret

Нам нужно скопировать эти 2 поля и перейти к Keycloak. Здесь нам нужно во вкладке, где мы копировали Redirect URI вставить недостающие поля.

Client ID - скопированный Client ID с GitHub.

Client Secret - скопированный Client Secret с GitHub.

Заполнение полей
Заполнение полей

Настройка Keycloak в Spring

Этот модуль посвящен интеграции Keycloak в Spring приложение.

Для начала нужно прописать свойства в application.yml. Здесь будет указываться, что мы используем в качестве ресурс сервера и клиента наш Keycloak.

application.yml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://localhost:9090/realms/OauthTests
      client:
        provider:
          keycloak:
            issuer-uri: http://localhost:9090/realms/OauthTests
            user-name-attribute: preferred_username
        registration:
          keycloak:
            client-id: myclient
            client-secret: YOUR SECRET
            scope: openid
server:
  port: 8083
logging:
  level:
    org.springframework.security: TRACE

client-id - это client id, созданного нами client в Keycloak.

client-secret - это секретный ключ, созданного нами client в Keycloak. (Мы его использовали выше, для теста пользователя)

Далее нам нужно создать класс SecurityConfig, чтобы настроить доступ к URI и реализовать oauth2 Resource Server. Это прописывается в методе securityFilterChain.

SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/error").permitAll()
                        .requestMatchers("/manager.html").hasRole("MANAGER")
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer((oauth2) -> oauth2
                        .jwt(Customizer.withDefaults())
                )
                .oauth2Login(Customizer.withDefaults());
        return http.build();
    }
}

@Configuration: Указывает, что класс содержит определения бинов и может быть использован контейнером Spring для генерации бинов.

@EnableWebSecurity: Включает поддержку безопасности веб-приложений в Spring Security.

authorizeHttpRequests: Определяет правила авторизации для HTTP-запросов.

.requestMatchers("/error").permitAll(): Позволяет доступ ко всем запросам на /error без аутентификации.

.requestMatchers("/manager.html").hasRole("MANAGER"): Разрешает доступ к /manager.html только пользователям с ролью MANAGER.

.anyRequest().authenticated(): Все остальные запросы требуют аутентификации.

oauth2ResourceServer: Настраивает приложение как OAuth2 ресурсный сервер, используя JWT для проверки токенов.

.jwt(Customizer.withDefaults()): Указывает на использование JWT с настройками по умолчанию.

oauth2Login: Включает OAuth2 логин с настройками по умолчанию, позволяя пользователям входить в систему с помощью провайдера OAuth2.


Далее создадим метод jwtAuthenticationConverter. Он конвертирует JWT в объект аутентификации, позволяя извлекать и преобразовывать роли из токена.

jwtAuthenticationConverter()
@Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtAuthenticationConverter.setPrincipalClaimName("preferred_username");
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
            var authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
            var roles =jwt.getClaimAsStringList("spring_sec_roles");

            return Stream.concat(authorities.stream(),
                    roles.stream()
                            .filter(role -> role.startsWith("ROLE_"))
                            .map(SimpleGrantedAuthority::new)
                            .map(GrantedAuthority.class::cast))
                    .toList();
        });
        return jwtAuthenticationConverter;
    }

.setPrincipalClaimName("preferred_username"): Указывает, что preferred_username будет использоваться как имя пользователя.

jwtGrantedAuthoritiesConverter: Конвертирует роли из JWT в объекты GrantedAuthority.

roles: Извлекает роли из токена, находящегося в поле spring_sec_roles.

Stream.concat: Объединяет роли из токена и стандартные авторитеты, преобразуя их в список авторитетов.


oAuth2UserService()
@Bean
    public OAuth2UserService<OidcUserRequest, OidcUser> oAuth2UserService() {
        var oidcUserService = new OidcUserService();
        return userRequest -> {
            var oidcUser = oidcUserService.loadUser(userRequest);
            var roles = oidcUser.getClaimAsStringList("spring_sec_roles");
            var authorities = Stream.concat(oidcUser.getAuthorities().stream(),
                            roles.stream()
                                    .filter(role -> role.startsWith("ROLE_"))
                                    .map(SimpleGrantedAuthority::new)
                                    .map(GrantedAuthority.class::cast))
                    .toList();

            return new DefaultOidcUser(authorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
        };
    }

OidcUserService: Сервис для обработки аутентификации OpenID Connect.

roles: Извлекает роли из аутентификационного ответа OpenID Connect.

Stream.concat: Объединяет роли из OpenID Connect и стандартные авторитеты, создавая новый объект DefaultOidcUser с обновленным списком авторитетов.

Целый код класса SecurityConfiig.

SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .authorizeHttpRequests((authorize) -> authorize
                        .requestMatchers("/error").permitAll()
                        .requestMatchers("/manager.html").hasRole("MANAGER")
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer((oauth2) -> oauth2
                        .jwt(Customizer.withDefaults())
                )
                .oauth2Login(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtAuthenticationConverter.setPrincipalClaimName("preferred_username");
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
            var authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
            var roles =jwt.getClaimAsStringList("spring_sec_roles");

            return Stream.concat(authorities.stream(),
                    roles.stream()
                            .filter(role -> role.startsWith("ROLE_"))
                            .map(SimpleGrantedAuthority::new)
                            .map(GrantedAuthority.class::cast))
                    .toList();
        });
        return jwtAuthenticationConverter;
    }

    @Bean
    public OAuth2UserService<OidcUserRequest, OidcUser> oAuth2UserService() {
        var oidcUserService = new OidcUserService();
        return userRequest -> {
            var oidcUser = oidcUserService.loadUser(userRequest);
            var roles = oidcUser.getClaimAsStringList("spring_sec_roles");
            var authorities = Stream.concat(oidcUser.getAuthorities().stream(),
                            roles.stream()
                                    .filter(role -> role.startsWith("ROLE_"))
                                    .map(SimpleGrantedAuthority::new)
                                    .map(GrantedAuthority.class::cast))
                    .toList();

            return new DefaultOidcUser(authorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
        };
    }
}

На этом самая базовая настройка Keycloak закончена. Теперь мы можем перейти к тестам.

Тестирование

Для проверки создадим две html страницы. Первая страница будет доступна для всех авторизированных пользователей authenticated.html, а вторая будет видна только менеджерам manager.html. Это сделано для того, чтобы протестировать, работает ли разделение по ролям.

authenticated.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
  <h2>Привет, аутентифицированный пользователь</h2>
</body>
</html>

manager.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
  <h2>Hi manager</h2>
</body>
</html>

Далее запустим наш проект и перейдем на наш хост - http://localhost:8083.

Запуск проекта
Запуск проекта
Переход на URI
Переход на URI

Как видно, нас сразу же перебрасывает на форму авторизации Keycloak. В этой форме есть вход через username/email, а также через GitHub и Google. Попробуем войти под нашим пользователем.

Попытка входа
Попытка входа

Мы смогли зайти под нашим пользователем. Так как мы создали authenticated страничку, то давайте попробуем туда зайти, указав такой URI - http://localhost:8083/authenticated.html.

Попробуем зайти на страничку manager.

manager.html
manager.html

Как видно, мы не можем туда зайти из-за ограничений в доступе, так как у нашего пользователя нет роли менеджер.

Давайте присвоим ему эту роль. Для этого в Keycloak зайдем во вкладку Users и выберем нашего пользователя.

Добавление роли
Добавление роли
Выбор нашей роли
Выбор нашей роли

Далее выбираем фильтр "by realm roles" и тут выбираем нашу роль, которую мы создали ранее. Все, новая роль присвоена пользователю. Перезапустим приложение и попробуем войти заново на страничку менеджера.

manager.html
manager.html

Как видно, все работает!

Теперь проверим вход и регистрацию через Google и GitHub.

Так как мы не сделали страничку выхода, то сессию нужно завершить в самом Keycloak и перезапустить наше приложение. В Keycloak переходим во вкладку Users, выбираем нашего пользователя, переходим в сессии и завершаем их все.

Завершение сессии
Завершение сессии
Google

Далее вновь заходим на наш хост и в форме авторизации выбираем Google.

Авторизация через Google
Авторизация через Google

Далее выбираем наш аккаунт.

Вход через Google
Вход через Google

Как видно, все работает! Если зайти в Keycloak во вкладку Users, то мы увидим, что у нас добавился новый пользователь.

Users
Users

GitHub

Далее вновь заходим на наш хост и в форме авторизации выбираем GitHub.

Авторизация через GitHub
Авторизация через GitHub

Далее вводим наши данные от аккаунта.

Вход через GitHub
Вход через GitHub

Как видно, все работает! Если зайти в Keycloak во вкладку Users, то мы увидим, что у нас добавился новый пользователь.

Users
Users

Из тестирования видно, что все работает. Авторизация проходит по логину и паролю, а также через Google и GitHub.

Вывод

В данной статье я описал, как начать свою работу с Keycloak и интегрировать его в свой проект Spring. Я разобрал главные моменты:

  1. Создание своего realm.

  2. Создание нового client.

  3. Создание своей роли.

  4. Подключение сторонних API.

Данная инструкция поможет освоить Keycloak.

Весь код вы можете найти в моем GitHub.

Комментарии (18)


  1. TldrWiki
    07.12.2024 09:48

    При изменении политики доступа (добавить роли доступ к эндпойнту) придется менять код. Для этих целей на эндпойнту навешиваются ресурсы. Ресурсы также привязываются к роли. В этом случае достаточно добавить/удалить ресурс в роли для изменения политики доступа.

    Но здесь у Кейклоака начинаются сложности. Связаны они с миграцией (необходимость переноса на новый сервер уже настроенной конфигурацией) раз. Со сложностью получения ресурсов из ролей на стороне бека (не проще ли использовать для этого собственное решение) два. И с использованием на фронте (официальная библиотека не поддерживает авторизацию).


    1. ivan_storozhev Автор
      07.12.2024 09:48

      Спасибо за ваш комментарий! Это действительно важные вопросы, которые возникают при использовании Keycloak в реальных условиях.

      1. Миграция конфигурации: Да, перенос настроек Keycloak на новый сервер сложный процесс. Один из подходов к решению этой проблемы — использование инструментов для автоматизации миграции, таких как Keycloak Export/Import или Terraform для описания инфраструктуры в виде кода. К сожалению, я пока что с этим не сталкивался, поэтому никак не описывал данную проблему, да и данная статья больше подходит для начинающих, которые недавно узнали о keycloak.

      2. Получение ресурсов из ролей на бэкенде: В некоторых случаях разработка кастомного решения может быть более эффективной, особенно если ваши требования отличаются от типичных сценариев использования Keycloak. Однако, при этом важно учитывать время и ресурсы, которые понадобятся на разработку и поддержку такого решения. В дальнейшем, я могу написать статью, где буду использовать кастомную реализацию!

      3. Использование на фронте: Ограниченная поддержка Keycloak для авторизации на фронтенде может стать проблемой, особенно если вы используете технологии, которые официальная библиотека не поддерживает. В таких случаях можно рассмотреть использование сторонних библиотек или даже разработку собственного решения, которое будет интегрироваться с Keycloak через его API. Я не особо знаком с фронтом, поэтому также не стал затрагивать эту область!


      1. Smerig
        07.12.2024 09:48

        Фронт нужен только лишь для того, чтобы обращаться к своему ресурс серверу с передачей RPT (requesting party token). Тут и появляется главное предназначение keycloak - UMA (user managed access). Использование Keycloak в качестве authorization server - это, по моему, больше вспомогательная роль. Т.к. наверняка на рынке есть куча подобных решений.


        1. TldrWiki
          07.12.2024 09:48

          Вот именно этот механизм в новых версиях Кейклоака сломан. Чтобы фронт не палил secret сделана возможность аутентификации через публичного клиента. Полученный от публичного клиента токен фронт отправляет беку и бек идет обменивать его на приватный токен с ресурсами (в приватном клиенте) и получает ошибку.


          1. Smerig
            07.12.2024 09:48

            Тут вступает в силу UMA. Вы с токеном с публичного клиента идете в бек, бек идет в keycloak с запросом ресурса, получает permission ticket и возвращает его на фронт. Фронт берет тикет и обменивает его на новый токен, уже RPT. И уже с этим RPT идет на бек.

            Возможно вы имели в виду то же самое. Но тогда я не понимаю, чего там сломано. Прямо сейчас делаю эту процедуру в Keycloak 26.


            1. ivan_storozhev Автор
              07.12.2024 09:48

              После некоторого блока статей, я вернусь к этой теме и постараюсь также детально разобрать этот случай. Спасибо за ваш комментарий !!


            1. TldrWiki
              07.12.2024 09:48

              Я уже все грабли сломал. То что вы описываете это для одного ресурса? То есть при каждом запросе бек идет за permission ticket?

              У меня другая ситуация. Я пытаюсь обменять публичный токен на rpt со всеми ресурсами, чтобы каждый раз не ходить за токеном. И до 22 версии это работало, но потом перестало.


              1. Smerig
                07.12.2024 09:48

                для меня тоже интересен вариант, когда ресурсов тысячи. Но, как я понял, ресурсы можно в токен дописывать. Допустим, штук 50, мне кажется, в токене будет ненапряжно таскать. В общем, прочитайте, тут найдете ответы на все вопросы https://www.keycloak.org/docs/latest/authorization_services/index.html. Пока я целиком не прочитал, рабочего варианта получить не мог.


    1. DMY
      07.12.2024 09:48

      Класс, уже не только стати но и ответы пишут с помощью чатгпт


      1. Smerig
        07.12.2024 09:48

        не стоит полагаться на чатгпт в вопросе keycloak, многое приходится делать по-другому.


        1. ivan_storozhev Автор
          07.12.2024 09:48

          В самой статье данный ресурс никак не был задействован, все писалось самостоятельно на основе личного опыта. Ответ на комментарий действительно отчасти был сгенерирован при помощи гпт, но это было сделано из-за спешки, так как не хотелось оставлять пользователя без ответа. Больше таких ответов как можете заметить нет. Хотелось бы извиниться за такой неприятный случай!


  1. Raspy
    07.12.2024 09:48

    Жаль что в основном статьи для новичков. Что-то посложнее уже почти никто не разбирает. Например всякие аутентификации с помощью физического токена, керберос, по сертификату или северная аутентификация по mTLS без кредов ну и тд и пт. Рассмотрение цепочек аутентификаций с несколькими факторами, в том числе с проверкой сети, подтверждение по смс, устройства, биометрия..


    1. ivan_storozhev Автор
      07.12.2024 09:48

      Спасибо за ваш комментарий!

      Я с вами полностью согласен, в наших реалиях действительно больше статей для новичков, но многие технологии все равно разобраны не так детально, как это попытался сделать я. Когда я сам изучал Keycloak, я столкнулся с проблемой, что многие упускают важные детали, которых мне не хватало для полного понимания.

      В дальнейшем я планирую делать более подробные статьи, которые касаются не только новичков, но и специалистов с опытом!


  1. Smerig
    07.12.2024 09:48

    Все, что касается этой статьи, описано тут https://www.keycloak.org/docs/latest/authorization_services/index.html.


  1. Smerig
    07.12.2024 09:48

    Лучше напишите какую-нибудь достаточно серьезную статью. С базовыми настройками любой разберется. Либо предложите какое-нибудь неочевидное решение. Или как вы решаете проблему доступа к конкретным ресурсам, допустим, их у вас тысячи. Таскаете ли вы их все в RPT или у вас там только последние N штук. А сколько N штук лучше всего хранить в токене?


    1. ivan_storozhev Автор
      07.12.2024 09:48

      Спасибо за ваш комментарий!

      С тем, что тут описаны базовые настройки, я соглашусь, а точнее я прямым текстом в статье об этом пишу. Вы скинули ссылку на документацию по Keycloak, я ее просто упростил и показал все на примерах, чтобы начинающие специалисты быстрее разобрались с этой технологией. Я согласен с вами, что более серьезную статью тоже надо написать, но я бы хотел к этому подойти постепенно. Я хочу, чтобы мои статьи приносили пользу как для начинающих, так и для опытных специалистов, и чтобы каждая статья могла дополнить новую! Еще раз спасибо за ваш комментарий!


  1. LeonidIvanov123
    07.12.2024 09:48

    Не очень понял, для чего используется зависимость

    <artifactId>keycloak-admin-client</artifactId>?

    Судя по статье - зависимость не нужна.


    1. ivan_storozhev Автор
      07.12.2024 09:48

      Да, вы правы, данная зависимость никак не используется в данной статье, однако, если вы хотите настраивать Keycloak под себя, то она потребуется. Возможно в последующих статьях я про это расскажу, а пока что эту зависимость можно не прописывать. Спасибо за ваш комментарий !