Получаю ошибку 403 при попытке выполнить запрос с Bearer токеном в Spring Boot 3.4.2 + Keycloak 26.0.4

Пытаюсь настроить авторизацию в приложении Spring Boot, использующем Keycloak для аутентификации. Когда я отправляю GET-запрос с Bearer токеном, получаю ответ с ошибкой 403 (Forbidden). В настройках Spring Security разрешены только пользователи с ролью member, но почему-то запрос не проходит.

  • Spring Boot - 3.4.3
  • Keycloak - 26.0.4.

Maven (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.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
        <checkstyle.version>7.8.1</checkstyle.version>
        <jacoco.version>0.8.4</jacoco.version>

        <testcontainers.version>1.18.3</testcontainers.version>
        <jooq.version>3.18.3</jooq.version>
        <postgresql.jdbc.version>42.6.0</postgresql.jdbc.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- OpenAPI 3 -->
            <dependency>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi</artifactId>
                <version>2.8.5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>


        <!-- Keycloak -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-admin-client</artifactId>
            <version>26.0.4</version>
        </dependency>


        <!-- Springdoc OpenAPI для интеграции с Spring Boot -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!-- Runtime library -->
        <dependency>
            <groupId>com.github.therapi</groupId>
            <artifactId>therapi-runtime-javadoc</artifactId>
            <version>0.15.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <plugin>
                <groupId>org.springdoc</groupId>
                <artifactId>springdoc-openapi-maven-plugin</artifactId>
                <version>1.4</version>
            </plugin>
        </plugins>
    </build>
</project>

Spring Security Configuration

@Configuration
@EnableWebSecurity
public class SSOConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.cors(Customizer.withDefaults());
        http.csrf(AbstractHttpConfigurer::disable);
        http.authorizeHttpRequests(registry -> {
            registry
                    .requestMatchers("/swagger-ui/**").permitAll()
                    .requestMatchers("/swagger-ui.html").permitAll()
                    .requestMatchers("/v3/api-docs/**").permitAll()
                    .requestMatchers("/user/register/exception").permitAll()
                    .requestMatchers(HttpMethod.GET).hasRole("member");
        }).oauth2Login(Customizer.withDefaults());
        return http.build();
    }
}

application.properties

spring.security.oauth2.client.provider.keycloak.issuer-uri=http://127.0.0.1:8080/realms/opposite
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.client-id=backend
spring.security.oauth2.client.registration.keycloak.client-secret=XXXX
spring.security.oauth2.client.registration.keycloak.scope=openid

Авторизация в Keycloak

curl -X POST --location "http://127.0.0.1:8080/realms/opposite/protocol/openid-connect/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d 'client_id=frontend&username=75051519551&password=9561&grant_type=password'  

Полученный токен

{
  "exp": 1742795683,
  "iat": 1742795383,
  "jti": "4ce57c6b-0269-456a-a081-1cc3c4dfeb94",
  "iss": "http://127.0.0.1:8080/realms/opposite",
  "aud": "frontend",
  "sub": "be93a350-64fe-44a4-b102-ae1b1684e7cc",
  "typ": "bearer",
  "azp": "backend",
  "sid": "1cdb16ec-f003-46ce-bd2a-baa1ef34c33a",
  "acr": "1",
  "allowed-origins": [
    "/*"
  ],
  "realm_access": {
    "roles": [
      "member"
    ]
  },
  "resource_access": {
    "backend": {
      "roles": [
        "member"
      ]
    },
    "frontend": {
      "roles": [
        "member"
      ]
    }
  },
  "scope": "email profile",
  "email_verified": true,
  "name": "Иван Иванов",
  "preferred_username": "75051519551",
  "given_name": "Иван",
  "family_name": "Иванов",
  "email": "[email protected]"
}

Запрос на backend*

curl -X GET --location "http://127.0.0.1:8081/api/treasure/v1/transaction?name=qw&offset=0&numberOfRows=10" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer XXX"

Получаю ответ с ошибкой 403 (Forbidden) при попытке выполнить запрос с Bearer токеном, даже несмотря на то, что роль member присутствует в токене. Я проверил, что токен корректен.


Ответы (0 шт):