Как реализовать аутентификацию через Keycloak в Spring Security?

Ответ

Интеграция Spring Security с Keycloak позволяет делегировать аутентификацию и авторизацию внешнему OAuth 2.0 / OpenID Connect (OIDC) провайдеру.

Базовая настройка:

  1. Добавление зависимостей (Maven):

    <!-- Стартер Keycloak для Spring Boot -->
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-spring-boot-starter</artifactId>
    </dependency>
  2. Конфигурация в application.yml:

    keycloak:
      realm: my-application-realm
      auth-server-url: ${KEYCLOAK_URL:http://localhost:8080/auth} # URL сервера Keycloak
      resource: my-spring-backend-client # Client ID в Keycloak
      public-client: false # Для конфиденциальных клиентов (backend)
      credentials:
        secret: ${KEYCLOAK_CLIENT_SECRET} # Секрет клиента
      ssl-required: external
      use-resource-role-mappings: true
  3. Конфигурация безопасности (современный подход без устаревшего адаптера):

    import org.springframework.context.annotation.Bean;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
    import org.springframework.security.web.SecurityFilterChain;
    
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            // Конвертер для преобразования ролей из JWT-токена Keycloak
            JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
            jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakJwtRolesConverter());
    
            http
                .authorizeHttpRequests(authz -> authz
                    .requestMatchers("/api/public/**").permitAll()
                    .requestMatchers("/api/admin/**").hasRole("ADMIN") // Роли из Keycloak
                    .requestMatchers("/api/user/**").hasRole("USER")
                    .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                    .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter))
                ); // Валидация JWT-токенов через Keycloak
            return http.build();
        }
    }
  4. Защита эндпоинта:

    @RestController
    @RequestMapping("/api/user")
    public class UserController {
    
        @GetMapping("/profile")
        // @PreAuthorize("hasRole('USER')") // Альтернатива конфигурации в SecurityFilterChain
        public String getUserProfile() {
            return "User profile data";
        }
    }

Как это работает:

  1. Клиент (frontend) аутентифицируется в Keycloak и получает JWT-токен (Access Token).
  2. Клиент отправляет этот токен в заголовке Authorization: Bearer <token> запроса к Spring Boot приложению.
  3. Spring Security (oauth2ResourceServer) проверяет подпись токена, извлекает claims (роли, имя пользователя) и создает объект аутентификации.
  4. На основе ролей из токена (resource_access.<client>.roles) принимается решение о доступе к эндпоинту.

Преимущества: Централизованное управление пользователями и правами, поддержка SSO, стандартные протоколы безопасности.

Ответ 18+ 🔞

А, слушай, смотри, вот эта вся ваша интеграция Spring Security с Keycloak — это, блядь, как делегировать всю свою головную боль по логинам-паролям какому-то внешнему дяде. Типа, «на, ёпта, сам разбирайся, кто тут пользователь, а кто — пидарас шерстяной». И дядька этот — OAuth 2.0 / OpenID Connect провайдер, в нашем случае — Keycloak.

Ну, начинаем колдовать, блядь:

  1. Сперва жмакаем в проект нужную хуйню (Maven):

    <!-- Вот этот самый стартер, чтобы Keycloak в Spring Boot впендюрился -->
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-spring-boot-starter</artifactId>
    </dependency>

    Без этого нихуя не взлетит, это как без билета в еперный театр.

  2. Потом в application.yml пишем заклинание:

    keycloak:
      realm: my-application-realm # Твоё королевство в Keycloak
      auth-server-url: ${KEYCLOAK_URL:http://localhost:8080/auth} # Адрес этого самого дядьки
      resource: my-spring-backend-client # Твой паспорт (Client ID)
      public-client: false # Ты не публичная шлюха, а конфиденциальный клиент (backend же!)
      credentials:
        secret: ${KEYCLOAK_CLIENT_SECRET} # Секретное слово, типа «сим-сим, откройся»
      ssl-required: external
      use-resource-role-mappings: true

    Главное — секрет (secret) не потеряй, а то любой левый чувак сможет прикинуться твоим бэкендом. Доверия ебать ноль будет.

  3. Теперь конфиг безопасности (современный, без старых адаптеров, которые уже в утиль):

    import org.springframework.context.annotation.Bean;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
    import org.springframework.security.web.SecurityFilterChain;
    
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            // Вот этот преобразователь — чтобы роли из токена Keycloak вытащить
            JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
            jwtConverter.setJwtGrantedAuthoritiesConverter(new KeycloakJwtRolesConverter()); // Свой конвертер для ролей
    
            http
                .authorizeHttpRequests(authz -> authz
                    .requestMatchers("/api/public/**").permitAll() // Сюда пускаем всех, даже кота
                    .requestMatchers("/api/admin/**").hasRole("ADMIN") // Только для избранных
                    .requestMatchers("/api/user/**").hasRole("USER")   // Для простых смертных
                    .anyRequest().authenticated() // Всё остальное — только со пропуском
                )
                .oauth2ResourceServer(oauth2 -> oauth2
                    .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter))
                ); // И вот тут магия: Spring будет проверять JWT-токены через Keycloak
            return http.build();
        }
    }
  4. Ну и контроллер, который теперь под защитой:

    @RestController
    @RequestMapping("/api/user")
    public class UserController {
    
        @GetMapping("/profile")
        // Можно и так: @PreAuthorize("hasRole('USER')")
        public String getUserProfile() {
            return "User profile data";
        }
    }

    Без правильного токена на этот эндпоинт — получишь, вротберунчик, отлуп от Spring Security.

А работает вся эта хуйня так, блядь:

  1. Какой-то фронтенд (или мобила) идёт, кланяется Keycloak, говорит: «Пусти, я свой». Keycloak его проверяет и выдаёт JWT-токен (это такая цифровая бумажка, что ты — не левый человек).
  2. Этот фронтенд, довольный как слон, лезет к твоему Spring Boot приложению и суёт эту бумажку в заголовок: Authorization: Bearer <вот_этот_токен>.
  3. Spring Security его видит и такой: «О, ёклмн, токен!». Он не сам его разбирает, а спрашивает у Keycloak (или проверяет подпись): «Дядь, это твоя работа?». Keycloak кивает. Spring тогда вытаскивает из токена claims — кто ты, какие у тебя роли, и создаёт внутри объект аутентификации.
  4. И уже по этим самым ролям (resource_access.<client>.roles внутри токена) решает: пускать тебя в /api/admin или послать нахуй с сообщением 403.

И в чём, сука, кайф? А кайф в том, что пользователей и их права теперь не в своей базе душить, а в одном месте — в Keycloak. Single Sign-On (SSO) работает, протоколы стандартные, безопасность. В общем, не еби себе мозг, пусть Keycloak парится.