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

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

В данной статье мы рассмотрим процесс интеграции Keycloak в наше приложение Spring Boot 3 в качестве сервера авторизации с использованием протокола OAuth2. Обсудим смысл OAuth2, его механизм работы и сравним его с другими протоколами. Кроме того, мы настроим Keycloak с использованием Docker Compose, воспользовавшись PostgreSQL в качестве базы данных для Keycloak. Затем мы интегрируем Keycloak с нашим приложением Spring Boot 3, используя протокол OAuth2. Также мы подключим Keycloak Admin Client и, наконец, проверим функциональность всей системы.

OAuth 2.0: Механизм работы OAuth2 и его структура

OAuth2 является протоколом авторизации, предназначенным для делегирования доступа к ресурсам от имени пользователя без передачи ему его учетных данных. Протокол поддерживает различные потоки (grant types), включая Authorization Code, Implicit, Resource Owner Password Credentials и Client Credentials, что делает его гибким для различных сценариев использования. Основная идея OAuth2 заключается в том, чтобы позволить пользователям предоставлять доступ к своим данным на сторонних ресурсах, не раскрывая свои учетные данные. Вместо этого, для аутентификации используются токены, которые выдаются после успешной авторизации. Такими токенами являются Access Token (Токен доступа) и Refresh Token (Токен обновления). Access токен предоставляет приложению временный доступ к ресурсам на сервере. Он имеет ограниченный срок действия, чаще всего короткий (несколько минут или часов). Access токен передается при каждом запросе к защищенному ресурсу. Refresh токен используется для обновления Access токена после его истечения срока действия. У Refresh токена обычно более длительный срок действия, чем у Access токена. Он предназначен для длительного использования. Когда Access токен истекает, приложение отправляет Refresh токен на сервер авторизации и сервер возвращает новый Access токен. В контексте OAuth2 существуют специальные серверы авторизации, также известные как Authorization Servers. Эти серверы играют ключевую роль в процессе выдачи токенов и управлении доступом. Различные веб-сервисы, такие как Google, Facebook, GitHub и другие, являются примерами таких серверов. Основными компонентами механизма OAuth 2 являются:

  1. Resource Owner (Владелец ресурса): Пользователь, владеющий данными (ресурсами), к которым запрашивается доступ.

  2. Client (Клиент): Приложение или сервис, запрашивающий доступ к ресурсам у владельца.

  3. Authorization Server (Сервер авторизации): Сервер, управляющий процессом авторизации и выдачей токенов.

  4. Resource Server (Сервер ресурсов): Сервер, управляющий защищенными ресурсами, к которым запрашивается доступ.

Механизм работы OAuth2

тапни для увеличения
тапни для увеличения

    1. Регистрация приложения (Client Registration):

Клиент (ваше веб-приложение или сервис) должен быть предварительно зарегистрирован на сервере авторизации. В этот момент вы получаете идентификатор клиента (Client ID) и секрет (Client Secret), которые используются для аутентификации вашего приложения.

   2. Для получения доступ к защищенному ресурсу веб-приложение направляет пользователя на страницу авторизации Keycloak.

   3.  Подтверждение авторизации (User Authentication):

Пользователь вводит свои учетные данные на странице сервера авторизации и подтверждает предоставление доступа вашему приложению.

   4․ Получение кода авторизации (Authorization Code):

После успешной аутентификации сервер авторизации направляет пользователя обратно на ваш сайт по указанному вами URL-перенаправления, при этом включается код авторизации.

   5․ Обмен кода на токены (Token Exchange):

Клиент использует полученный код для запроса Access Token и, при необходимости, Refresh Token у сервера авторизации. Запрос выглядит примерно так:

  6․ Выдача токенов (Token Issuance):

После успешной проверки кода сервер авторизации отвечает JSON-объектом, который содержит Access Token и, возможно, Refresh Token.

 7. Доступ к ресурсам (Access Resources):

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

 8․ Обновление токена (Token Refresh):

При истечении срока действия Access Token клиент может использовать Refresh Token для получения нового Access Token без повторной аутентификации владельца ресурса.

Сравнение Oauth2 с другими подходами авторизации

Критерий

OAuth 2.0

OpenID Connect

SAML

Basic

Передача пароля

Не рекомендуется, используются маркеры доступа

(Access tokens)

 

Может предоставлять аутентификацию, включая передачу пароля

Используется для передачи атрибутов, включая аутентификацию

Основной метод, небезопасен при передаче через сеть

Передача ролей

Возможность передачи различных разрешений. Например, чтение данных, запись данных, выполнение определенных действий и т. д.

Может передавать информацию о ролях через атрибуты

Специализируется на передаче атрибутов, включая роли

Ограничен в передаче дополнительной информации о ролях

Простота реализации

Относительно простая реализация

Может быть более сложным из-за дополнительных функциональных возможностей

Может быть более сложным в сравнении с OAuth 2.0

Прост в реализации, но ограничен функциональностью

Поддержка устройств

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

Поддерживает различные сценарии, но может быть более сложным в реализации

Традиционно ориентирован на веб-приложения

Ограничен в поддержке современных сценариев использования

Расширяемость

Позволяет расширять функциональность через дополнительные спецификации и профили

Предоставляет расширенные возможности для удовлетворения специфических требований

Предоставляет возможность определения дополнительных профилей и расширений

Ограничен в расширяемости и возможностях

Oauth2 и Keykloack

В нашем случае, для обеспечения функциональности сервера авторизации, мы используем Keycloak. Keycloak предоставляет не только механизм выдачи токенов, но и обширные возможности управления идентичностью, включая аутентификацию, авторизацию и управление пользователями. Таким образом, при использовании Keycloak с OAuth2 ваше веб-приложение может делегировать процесс аутентификации и управление доступом к Keycloak, который выступает в роли вашего собственного сервера авторизации. Это обеспечивает высокий уровень безопасности и управляемости в процессе работы с данными пользователей, при этом не требуя передачи конфиденциальных учетных данных вашему веб-приложению.

Установка и настройка Keycloak

Начнем с установки Keycloak. Вы можете скачать его с его официального сайта https://www.keycloak.org/downloads.html и следовать инструкциям по установке для вашей операционной системы. Для удобства установки и развертывания, мы будем использовать будем использовать Docker Compose файл и последнюю версию докер образа Keycloak, которая на данный момент является 16.1.1. В зависимости от выбранной версии Keycloak могут отличаться пользовательский интерфейс и некоторые другие элементы.

Создайте файл docker-compose.yml в вашем проекте и добавьте следующий пример контента файла:
version: '3'

services:
  postgres:
    image: postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: keycloakdb
      POSTGRES_USER: keycloakuser
      POSTGRES_PASSWORD: keycloakpass
    networks:
      - keycloak-network
  keycloak:
    image: jboss/keycloak:16.1.1
    ports:
      - 8180:8080
    environment:
      DB_VENDOR: POSTGRES
      DB_ADDR: postgres
      DB_DATABASE: keycloakdb
      DB_SCHEMA: public
      DB_USER: keycloakuser
      DB_PASSWORD: keycloakpass
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: adminpass
    networks:
      - keycloak-network
    depends_on:
      - postgres

networks:
  keycloak-network:
    driver: bridge

volumes:
  postgres_data:
    driver: local

Keycloak по умолчанию использует встроенную базу данных H2. Однако для production развертывания рекомендуется использовать более надежную и масштабируемую базу данных, такую как PostgreSQL, MySQL или MariaDB. В предоставленном файле Docker Compose конфигурация настроена на использование PostgreSQL в качестве базы данных для Keycloak.

Запустите команду docker-compose up из директории проекта, где находится файл docker-compose.yml. После выполнения этой команды, сервер Keycloak начнет работу на порту 8180. Для доступа к административной консоли перейдите по следующей ссылке: http://localhost:8180/.

После этого введите администраторский логин и пароль, указанные в файле docker-compose.yml (admin, adminpass).

Создаем Realm:

"Realm" в Keycloak - это административная единица (контейнер), которая объединяет набор клиентов (applications), пользователей и настроек безопасности в рамках одной области. Каждое приложение в Keycloak принадлежит определенному realm, и realm предоставляет изолированное пространство для управления пользователями, аутентификации и авторизации.

После успешного входа, перейдите к следующему шагу и создайте новый realm, нажав "Add realm".

Укажите имя и нажмите "Create".

Клиент представляет собой ваше веб-приложение, которое будет взаимодействовать с Keycloak для аутентификации.

Перейдите к следующему шагу и внутри вашего Realm создайте клиента. В левом боковом меню перейдите в раздел "Clients" и нажмите "Create" в правом верхнем углу.
Создайте клиента, указав Client ID, и нажмите "Save".

Перейдём к настройке клиента.

Установите тип доступа (Access Type) на "confidential", отключите "Direct Access Grants Enabled", включите "Service Accounts Enabled", укажите допустимые перенаправления (Valid redirect URIs) как http://localhost:8080/* и нажмите "Save".

Перейдите к "Client Scopes" -> "Roles" -> "Mappers" -> "Realm Roles" и включите "Add to userinfo", затем нажмите "Save".

Добавим роли для клиента:

1.       Перейдите в раздел "Clients" и выберите конкретного клиента (например, "myclient").

2.       Внутри клиента перейдите в "Service Account Roles" -> "Client Roles".

3.       Нажмите на "Select a Client" и выберите "realm-management" из списка

4.       В разделе "Available Roles" найдите роли "manage-users", "query-users", "view-users", "view-realm". Нажмите на каждую из этих ролей, чтобы выделить их. Нажмите "Add Selected", чтобы добавить выбранные роли к служебной учетной записи вашего клиента.

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

С помощью импорта разработчики могут избежать необходимости повторного ввода одних и тех же настроек безопасности. Это снижает риск возникновения ошибок из-за человеческого фактора и ускоряет процесс разработки.
Для экспорта реалма необходимо перейти в раздел "Export" в левом боковом меню, включить опции "Export groups and roles" и "Export clients", затем нажать кнопку "Export".

Экспортированный из пользовательского интерфейса Keycloak файл не включает информацию о пользователях и секретах клиента. Однако существует метод добавления секрета в файл экспорта realm в формате JSON. Откройте его для редактирования.

Для добавления секрета клиента найдите клиента с секретом, который нужно сохранить (например, поиск по "clientId": "myclient"), затем найдите "secret": "**********" внутри него и замените звездочки на секрет клиента, который нужно сохранить.

После этого добавьте экспортированный файл в приложение и добавьте следующие изменения в docker-compose.yml файле
keycloak:
      KEYCLOAK_IMPORT: /opt/jboss/keycloak/standalone/configuration/realm-export.json
    volumes:
      - ./realm-export.json:/opt/jboss/keycloak/standalone/configuration/realm-export.json

Запустите команду docker-compose up из директории проекта, где находится файл docker-compose.yml.

Как создать пользователя:

Перейдите в раздел "Users".

Нажмите на "Add user", укажите желаемое имя пользователя (например, testuser) и нажмите "Save".

Перейдите в "Users" -> "testuser" -> "Credentials".

Укажите пароль, повторите пароль и нажмите "Set Password".

Опционально, можно отключить опцию "Temporary", чтобы при первом входе пользователь был обязан изменить пароль.

Как создать роли:

Перейдите в раздел "Roles".

Нажмите на "Add role", укажите название роли (например, admin) и нажмите "Save".

Как назначить роль пользователю:

Перейдите в раздел "Users".

Выберите нужного пользователя, например, "testuser".

Перейдите в "Role Mappings" -> "Realm roles" -> "Available".

Нажмите на нужную роль, например, "admin", и затем "Add Selected".

Группы. Как их создать

1. Организация Пользователей:

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

2. Управление Доступом:

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

3. Роли в Группах:

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

Для создания групп перейдите на вкладку "Groups", затем нажмите "New", укажите название группы и нажмите "Save".

Для добавления роли к группе перейдите на вкладку Role Mappings внутри группы, из Available roles выберите нужную роль, например, "admin", и затем "Add Selected".

Для добавления пользователя в группу перейдите на вкладку "Users", выберите нужного пользователя, затем перейдите на вкладку "Groups". Внутри профиля пользователя в разделе "Available groups" выберите нужную группу и нажмите "Join".

Как добавить дополнительные атрибуты в Access токен:

Access token содержит некоторые метаданные, но мы можем добавить в него атрибуты. Давайте посмотрим, как добавить пользовательский атрибут в токен. Перейдите на вкладку Users, выберите пользователя, затем откройте вкладку Attributes. Нажмите кнопку Add, добавьте ключ для атрибута (например, customAttribute) и значение (например, customAttributeValue), затем нажмите Save.

После этого нам нужно добавить маппер для этого атрибута. Чтобы сделать это, перейдите в раздел Clients, выберите свой клиент, затем перейдите на вкладку Mappers и нажмите Create. Укажите имя маппера (например, customMapper), установите тип маппера как User Attribute, выберите Claim JSON Type как string (вы можете выбрать другие типы в зависимости от ваших потребностей), укажите имя атрибута пользователя и имя атрибута токена как customAttribute, затем нажмите Save.

Теперь мы можем получать токен от Keycloak и там видеть атрибут, который мы добавили.

В этой части статьи мы установили и настроили Keycloak, подключили базу данных для использования Keycloak, создали Realm, создали и настроили клиента, экспортировали и импортировали Realm, создали пользователей, создали роли, назначили роли пользователям, создали группу, добавили роль к группе, добавили пользователей в группу и добавили дополнительный атрибут в Access token. Если вы хотите использовать другую конфигурацию, добавить дополнительные настройки или выполнить другие действия, вы можете найти соответствующую информацию на официальной странице документации Keycloak по следующей ссылке https://www.keycloak.org/documentation.

Настройка безопасности приложения Spring MVC

Теперь перейдем к конфигурации нашего приложения Spring Boot и добавим безопасность.

Добавьте следующие зависимости:
 dependencies {
    // Другие зависимости вашего проекта
    // Зависимость для поддержки OAuth2-клиента в Spring Boot
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
    // Зависимость для поддержки безопасности в Spring Boot
    implementation 'org.springframework.boot:spring-boot-starter-security'
}

Теперь добавим конфигурационный класс для безопасности (security).

Тут большая страшилка
import static org.springframework.security.config.Customizer.withDefaults;
import java.net.URI;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true
)
public class SecurityConfig {
  
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler) throws Exception {
        return http.authorizeHttpRequests(authorise ->
                        authorise
                                .requestMatchers("/static/**")
                                .permitAll()
                                .anyRequest()
                                .authenticated())
                .csrf(AbstractHttpConfigurer::disable)
                .exceptionHandling(authorise -> authorise.accessDeniedPage("/access-denied"))
                .oauth2Login(withDefaults())
                .logout(logout ->
                        logout.logoutSuccessHandler(oidcLogoutSuccessHandler)).build();
}

    @Bean
    @SuppressWarnings("unchecked")
    public GrantedAuthoritiesMapper userAuthoritiesMapper() {

        return (authorities) -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
            authorities.forEach(authority -> {
                if (authority instanceof OidcUserAuthority oidcUserAuthority) {
                    OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
                    Map<String, Object> realmAccess = userInfo.getClaim("realm_access");
                    Collection<String> realmRoles;
                    if (realmAccess != null
                            && (realmRoles = (Collection<String>) realmAccess.get("roles")) != null) {
                        realmRoles
                                .forEach(role -> mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)));
                    }
                }
            });
            return mappedAuthorities;
        };
    }

    @Bean
    OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler(ClientRegistrationRepository clientRegistrationRepository) {
        OidcClientInitiatedLogoutSuccessHandler successHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
        successHandler.setPostLogoutRedirectUri(URI.create("http://localhost:8080").toString());
        return successHandler;
    }

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

} 

В файле application.yml добавим следующие настройки
spring:
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: myrealm
            client-secret: chnageme
            scope: openid, profile
            authorization-grant-type: authorization_code
        provider:
          keycloak:
            issuer-uri: http://localhost:8180/auth/realms/myrealm
            user-name-attribute: preferred_username
keycloak:
  server-url: http://localhost:8180/auth
  realm: myrealm
  username: admin
  password: adminpass

Замените значение changeme на уникальный секрет, который был сгенерирован для вашего клиента в Keycloak. Чтобы сгенерировать новый client-secret, перейдите в меню 'Clients'. В разделе 'Credentials' скопируйте существующий client-secret или, если его нет, нажмите 'Regenerate Secret', чтобы получить новое значение. Затем вставьте его в качестве значения для параметра client-secret в файле конфигурации вашего приложения (application.yml).

Отметим, что данная конфигурация предназначена для архитектуры MVC, когда наше приложение выступает в роли клиента и ресурсного сервера. Если ваше приложение является REST приложением и предназначено только для роли ресурсного сервера, вы можете найти соответствующую конфигурацию по следующим ссылкам:

https://github.com/Pask423/keycloak-springboot/tree/master/base-integration-spring-boot-3

https://www.youtube.com/watch?v=vmEWywGzWbA

Программное взаимодействие с Keycloak через Admin Client

Admin Client в Keycloak позволяет программно создавать пользователей и проводить разнообразные операции, обеспечивая автоматизацию административных задач без необходимости использования веб-консоли. Это удобство позволяет эффективно управлять идентификацией и доступом в системе.

Для использования админ клиента необходимо добавить следующий зависимость:

'org.keycloak:keycloak-admin-client:22.0.1'

Создай конфигурационый класс KeyCloakConfig, который вернет экземпляр Keycloak.
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class KeycloakConfig {
  
    @Value("${keycloak.server-url}")
    private String serverUrl;
    @Value("${keycloak.realm}")
    private String realm;
    @Value("${spring.security.oauth2.client.registration.keycloak.client-id}")
    private String clientId;
    @Value("${spring.security.oauth2.client.registration.keycloak.client-secret}")
    private String clientSecret;
    @Value("${keycloak.username}")
    private String userName;
    @Value("${keycloak.password}")
    private String password;

    @Bean
    public Keycloak keycloak() {
      
        return KeycloakBuilder.builder()
                .serverUrl(serverUrl)
                .realm(realm)
                .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
                .clientId(clientId)
                .clientSecret(clientSecret)
                .username(userName)
                .password(password)
                .build();
    }

}

Тестирование функционала

Для тестирования всего функционала создаем:

Простой Controller, DTO и Service
import jakarta.annotation.security.RolesAllowed;
import java.security.Principal;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
@RequiredArgsConstructor
public class MyController {
  
    private final KeyCloakService keyCloakService;

    @GetMapping("/admin")
    @RolesAllowed("admin")
    public String admin(Principal principal, Model model) {
        model.addAttribute("username", principal.getName());
        return "admin";
    }

    @GetMapping("/user")
    public String user(Principal principal, Model model) {
        model.addAttribute("username", principal.getName());
        return "user";
    }

    @GetMapping("/create")
    public String createUser() {
        return "create-user";
    }

    @PostMapping("/create")
    public String createUser(@RequestBody UserRequestDTO userRequestDTO) {
        keyCloakService.addUser(userRequestDTO);
        return "index";
    }

}


import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ErrorController {

    @GetMapping("/access-denied")
    public ModelAndView showAccessDeniedPage() {
        return new ModelAndView("access-denied");
    }

}


import lombok.Data;

@Data
public class UserRequestDTO {
    private String username;
    private String password;
    private String role;
}

 
import java.util.Collections;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;  

@Service
@RequiredArgsConstructor
public class KeyCloakService {

    private final Keycloak keycloak;

    @Value("${keycloak.realm}")
    private String realm;

    public void addUser(UserRequestDTO dto) {
        String username = dto.getUsername();
        CredentialRepresentation credential = createPasswordCredentials(dto.getPassword());
        UserRepresentation user = new UserRepresentation();
        user.setUsername(username);
        user.setCredentials(Collections.singletonList(credential));
        user.setEnabled(true);
        UsersResource usersResource = getUsersResource();
        usersResource.create(user);
        addRealmRoleToUser(username, dto.getRole());
    }

    private void addRealmRoleToUser(String userName, String roleName) {
        RealmResource realmResource = keycloak.realm(realm);
        List<UserRepresentation> users = realmResource.users().search(userName);
        UserResource userResource = realmResource.users().get(users.get(0).getId());
        RoleRepresentation role = realmResource.roles().get(roleName).toRepresentation();
        RoleMappingResource roleMappingResource = userResource.roles();
        roleMappingResource.realmLevel().add(Collections.singletonList(role));
    }

    private UsersResource getUsersResource() {
        return keycloak.realm(realm).users();
    }

    private static CredentialRepresentation createPasswordCredentials(String password) {
        CredentialRepresentation passwordCredentials = new CredentialRepresentation();
        passwordCredentials.setTemporary(false);
        passwordCredentials.setType(CredentialRepresentation.PASSWORD);
        passwordCredentials.setValue(password);
        return passwordCredentials;
    }

}

Html страницы

index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Keycloak Spring Boot Integration</title>
</head>
<body>
<h1>Welcome to Keycloak Integration App</h1>
<p><a th:href="@{/admin}">Admin Page</a></p>
<p><a th:href="@{/user}">User Page</a></p>
<p><a th:href="@{/create}">Create User</a></p>
<p><a th:href="@{/logout}">Logout</a></p>
</body>
</html>

access-denied.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Нет доступа</title>
</head>
<body>
<h1>You do not have permission to access this resource</h1>
<a href="index.html" th:href="@{~/}">
    <button>Return to home page</button>
</a>
</body>
</html>

admin.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Admin Page</title>
</head>
<body>
<h1>Welcome, <span th:text="${username}"></span>!</h1>
<p>This is the Admin Page.</p>
<p><a th:href="@{/}">Return to Home Page</a></p>
<p><a th:href="@{/logout}">Logout</a></p>
</body>
</html>

user.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User Page</title>
</head>
<body>
<h1>Welcome, <span th:text="${username}"></span>!</h1>
<p>This is the User Page.</p>
<p><a th:href="@{/}">Return to Home Page</a></p>
<p><a th:href="@{/logout}">Logout</a></p>
</body>
</html>

create-user.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Create user</title>
</head>
<body>
<form id="userForm" th:action="@{/create}" method="post">
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required>
    <br>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required>
    <br>
    <label for="role">Role:</label>
    <input type="text" id="role" name="role" required>
    <br>
    <button type="button" onclick="submitForm()">Create User</button>
</form>
<script>
    function submitForm() {
        var form = document.getElementById('userForm');
        var formData = {
            username: form.elements.username.value,
            password: form.elements.password.value,
            role: form.elements.role.value
        };
        var jsonData = JSON.stringify(formData);
        fetch(form.action, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: jsonData
        });
    }
</script>
</body>
</html>

Создаем двух пользователей в Keycloak с разными ролями, например, "firstuser" с ролью "admin" и "seconduser" с ролью "user".

В браузере перейдите по адресу http://localhost:8180/. Вас перенаправит на страницу аутентификации Keycloak. Войдите в систему как пользователь "firstuser".

Будет открываться главная страница

Нажмите на Admin Page и страница откроется успешно, так как пользователь "firstuser" имеет роль "admin".
Нажмите на Admin Page и страница откроется успешно, так как пользователь "firstuser" имеет роль "admin".
Нажмите на Logout для выхода из системы и войдите в систему как пользователь "seconduser".
Нажмите на Logout для выхода из системы и войдите в систему как пользователь "seconduser".

Нажмите на Admin Page и доступ будет запрещен, так как пользователь "seconduser" не имеет роль "admin"

Теперь программатично создаем пользователя с ролю. Нажмите на Create User и будет открываться страница для создания пользователя. Заполните поля 'username', 'password' и 'role' (выбрав одну из ролей, доступных в Keycloak), затем нажмите 'Create user'.

Перейдите в админ-консоль Keycloak по адресу localhost:8180, затем в меню 'Users' нажмите 'View All Users', и вы увидите созданного пользователя (testuser) с ролью 'admin'.

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

Заключение

Этот проект можно посмотреть на GitHub по следующей ссылке.

Несмотря на то, что Keycloak является мощным инструментом для управления идентификацией и доступом, у него есть некоторые недостатки:

1. Сложность настройки

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

2. Сложность обновлений

Переход на новые версии Keycloak иногда может быть сложным процессом из-за изменений в API и структуре данных.

В заключении отметим, что мы успешно произвели интеграцию Keycloak с протоколом OAuth 2 в приложении Spring Boot 3. Таким образом, наш опыт успешной интеграции Keycloak и OAuth 2 в Spring Boot 3 подтверждает, что данное решение не только соответствует высоким стандартам безопасности, но также обеспечивает удобство в разработке и поддержке нашего веб-приложения.

Мы можем подчеркнуть несколько важных моментов, касающихся интеграции Keycloak с протоколом OAuth 2 в приложении Spring Boot 3.

Во-первых, стоит отметить, что протокол OAuth 2 доказал свою эффективность в обеспечении безопасности и авторизации. Сравнив его с другими протоколами, мы выявили его гибкость и простоту в реализации, что делает его привлекательным выбором для многих сценариев авторизации.

В контексте интеграции с Keycloak, использование Docker Compose для настройки среды и PostgreSQL в качестве базы данных приносит дополнительные выгоды. Это позволяет эффективно управлять конфигурацией Keycloak, а также обеспечивает легкость масштабирования и переносимость между средами.

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

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

В итоге, интеграция Keycloak с протоколом OAuth 2, настроенного с использованием Docker Compose, в сочетании с программной интеграцией административного клиента в приложение Spring Boot 3, обеспечивает не только высокий уровень безопасности, но также максимальную степень автоматизации в управлении процессами аутентификации и авторизации.

Какие технологии вы используете для аутентификации и авторизации в своем Spring Boot приложении?

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


  1. remindscope
    18.12.2023 08:32

    Отличная статья, спасибо


  1. play_game_1817
    18.12.2023 08:32

    Здорово, очень подробные инструкции


  1. GrigorTumanyan
    18.12.2023 08:32

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


  1. Dmitry2019
    18.12.2023 08:32

    последнюю версию докер образа Keycloak, которая на данный момент является 16.1.1

    Последняя версия на сайте 23.0.3


    1. HakobDiloyan Автор
      18.12.2023 08:32

      Да, здесь есть упущение. Последняя версия докер образа Keycloak от Jboss является 16.1.1, который я использовал, а последняя версия quay.io/keycloak 23.0.3. Спасибо, что заметили.


      1. DonAlPAtino
        18.12.2023 08:32

        А какие причины использование старой версии? У меня вот user не создается с 400 Bad request. Пишут из-за несовпадения версий сервера и клиента. А тут такой немаленький gap выходит...


  1. aamatveef
    18.12.2023 08:32

    Отличная статья, было бы интересно в таком же формате почитать про Authorization в связке со Spring boot


  1. DonAlPAtino
    18.12.2023 08:32

    > В браузере перейдите по адресу http://localhost:8180/. Вас перенаправит на страницу аутентификации Keycloak

    8080 же. Мы же на спринговое приложение идем, а уже оно редиректит на keycloak


    1. HakobDiloyan Автор
      18.12.2023 08:32

      http://localhost:8180 - в этом случае ссылка на админ-консоль Keycloak. Для аутентификации должен быть пользователь, зарегистрированный в Keycloak, чтобы могли войти в систему через данные этого пользователя. Или можно создать регистрация пользователей, а затем использовать данные зарегистрированного пользователя для входа.