В современном мире веб-разработки обеспечение безопасности пользовательских идентификаторов и управление доступом к ресурсам становятся все более важными задачами. Один из мощных инструментов, предоставляющих полноценное решение для этих задач, это 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 являются:
Resource Owner (Владелец ресурса): Пользователь, владеющий данными (ресурсами), к которым запрашивается доступ.
Client (Клиент): Приложение или сервис, запрашивающий доступ к ресурсам у владельца.
Authorization Server (Сервер авторизации): Сервер, управляющий процессом авторизации и выдачей токенов.
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 и доступ будет запрещен, так как пользователь "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)
GrigorTumanyan
18.12.2023 08:32Спасибо за статью, было очень интересно, рекомендую к прочтению. Я потратил много времени на чтение, но оно того стоило.
Dmitry2019
18.12.2023 08:32последнюю версию докер образа Keycloak, которая на данный момент является 16.1.1
Последняя версия на сайте 23.0.3
HakobDiloyan Автор
18.12.2023 08:32Да, здесь есть упущение. Последняя версия докер образа Keycloak от Jboss является 16.1.1, который я использовал, а последняя версия quay.io/keycloak 23.0.3. Спасибо, что заметили.
DonAlPAtino
18.12.2023 08:32А какие причины использование старой версии? У меня вот user не создается с 400 Bad request. Пишут из-за несовпадения версий сервера и клиента. А тут такой немаленький gap выходит...
aamatveef
18.12.2023 08:32Отличная статья, было бы интересно в таком же формате почитать про Authorization в связке со Spring boot
DonAlPAtino
18.12.2023 08:32> В браузере перейдите по адресу http://localhost:8180/. Вас перенаправит на страницу аутентификации Keycloak
8080 же. Мы же на спринговое приложение идем, а уже оно редиректит на keycloak
HakobDiloyan Автор
18.12.2023 08:32http://localhost:8180 - в этом случае ссылка на админ-консоль Keycloak. Для аутентификации должен быть пользователь, зарегистрированный в Keycloak, чтобы могли войти в систему через данные этого пользователя. Или можно создать регистрация пользователей, а затем использовать данные зарегистрированного пользователя для входа.
remindscope
Отличная статья, спасибо