Кастомные эндпойнты

Запросы к приложению Jmix защищаются фреймворком Spring Security. В этом разделе объясняется, как настроить доступ к кастомным эндпойнтам приложения.

Общие сведения

Для глубокого понимания работы endpoint security, прочитайте соответствующие разделы документации Spring Security:

Spring Security использует специальные бины типа SecurityFilterChain для определения того, какие URL должны быть защищены. Каждый SecurityFilterChain конфигурируется с помощью билдера HttpSecurity. Приложение может иметь несколько бинов SecurityFilterChain. В этом случае очень важно указать их правильный порядок. См. раздел Multiple HttpSecurity Instances в документации Spring Security для информации о настройке нескольких объектов HttpSecurity.

Каждое приложение Jmix по умолчанию содержит конфигурацию безопасности, которая расширяет класс VaadinWebSecurity. Эта конфигурация настраивает доступ к внутренним эндпойнтам Vaadin и передает все запросы на авторизацию механизмам Jmix и Vaadin (предоставляет доступ к экранам с помощью аннотации на классе экрана или анализа ресурсных ролей пользователя). SecurityFilterChain, созданный этой конфигурацией, всегда имеет наименьший приоритет и всегда вызывается последним.

Дополнения, такие как OpenID Connect или Authorization Server, добавляют свои бины SecurityFilterChain, которые защищают эндпойнты авторизационного или ресурсного сервера. Эти бины всегда вызываются до тех, которые предоставлены модулем UI.

Безопасность кастомных эндпойнтов

Для определения кастомных правил безопасности для эндпойнтов объявите новый бин SecurityFilterChain. Важно, чтобы порядок этого бина был меньше, чем у бинов SecurityFilterChain, предоставленных фреймворком Jmix.

Вы можете найти значения порядка, используемые Jmix, в интерфейсе JmixSecurityFilterChainOrder. Практическое правило — использовать JmixSecurityFilterChainOrder.CUSTOM, JmixSecurityFilterChainOrder.CUSTOM - 10 и аналогичные значения для своих фильтров безопасности.

Пример простого бина SecurityFilterChain может выглядеть следующим образом:

@Bean
@Order(JmixSecurityFilterChainOrder.CUSTOM)
SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
    http.securityMatcher("/public/**")
            .authorizeHttpRequests(authorize ->
                    authorize.anyRequest().permitAll()
            );
    return http.build();
}

Данная конфигурация предоставляет доступ к всем запросам для эндпойнтов, соответствующих шаблону /public/**.

Примеры

Публичные эндпойнты

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

@RestController
public class GreetingController {

    @GetMapping("/greeting/hello")
    public String hello() {
        return "Hello!";
    }

    @PostMapping("/greeting/hi")
    public String hi() {
        return "Hi!";
    }
}

Доступ к публичным эндпойнтам можно настроить с помощью следующей конфигурации:

@Configuration
public class AnonymousControllerSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.CUSTOM) (1)
    SecurityFilterChain greetingFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/greeting/**") (2)
                .authorizeHttpRequests(authorize ->
                        authorize.anyRequest().permitAll() (3)
                )
                .csrf(csrf -> csrf.disable()); (4)
        JmixHttpSecurityUtils.configureAnonymous(http); (5)
        return http.build();
    }
}
1 JmixSecurityFilterChainOrder.CUSTOM — порядок меньше любого другого порядка безопасности фильтра Jmix.
2 securityMatcher() используется для определения, должен ли быть применен HttpSecurity к запросу. Matcher запроса из примера будет соответствовать запросам с URL, которые соответствуют шаблону /greeting/**. Запросы для других URL будут обрабатываться по умолчанию фильтром безопасности модуля Jmix UI.
3 Инструкция permitAll() предоставляет доступ к эндпойнтам.
4 Отключает CSRF для запросов POST.
5 Вызов JmixHttpSecurityUtils.configureAnonymous(http) настраивает анонимную аутентификацию, установив анонимного пользователя, возвращенного Jmix UserRepository, в контекст безопасности.

Аутентификация HTTP Basic

Данный пример демонстрирует, как защищать эндпойнты контроллера с помощью HTTP Basic аутентификации.

Класс контроллера:

@RestController
@RequestMapping("/api")
public class BasicGreetingController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello!";
    }

    @GetMapping("/public/hi")
    public String publicHi() {
        return "Hi!";
    }
}

Запросы с URL, соответствующими шаблону /api/**, должны быть защищены HTTP Basic аутентификацией, а запросы к /api/public/** должны быть доступны для любого пользователя. Этого можно достичь с помощью конфигурации ниже:

@Configuration
public class BasicControllerSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.CUSTOM) (1)
    SecurityFilterChain basicControllerFilterChain(
            HttpSecurity http,
            AuthenticationManager authenticationManager) throws Exception {
        http.securityMatcher("/api/**") (2)
                .authorizeHttpRequests(requests ->
                        requests
                                .requestMatchers("/api/public/**").permitAll() (3)
                                .anyRequest().authenticated() (4)
                )
                .httpBasic(Customizer.withDefaults()) (5)
                .authenticationManager(authenticationManager); (6)
        return http.build();
    }
}
1 JmixSecurityFilterChainOrder.CUSTOM — порядок меньше любого другого порядка безопасности фильтра Jmix.
2 Матчер безопасности указывает, что HttpSecurity будет применяться только для запросов /api/**.
3 Если запрос соответствует матчеру безопасности, то мы можем предоставить дополнительные правила. Все запросы для /api/public/** должны быть разрешены без аутентификации.
4 Все запросы, которые не соответствуют шаблону /api/public/**, должны быть аутентифицированы.
5 Включает HTTP Basic аутентификацию.
6 Использует AuthenticationManager, настроенный Jmix для HTTP Basic аутентификации.

Запросы к эндпойнтам /api/** должны содержать заголовок в формате Authorization: Basic <credentials>, где <credentials> — Base64-кодированная строка, состоящая из имени пользователя и пароля, разделенных одиночным знаком двоеточия. Например:

GET /api/hello HTTP/1.1
Host: server.example.com
Authorization: Basic YWRtaW46YWRtaW4=
В этом примере доступ к /api/public/** можно альтернативно настроить с помощью другого бина SecurityFilterChain, который имеет матчер безопасности /api/public/** и порядок меньше текущего, например, JmixSecurityFilterChainOrder.CUSTOM - 10.

Аутентификация с помощью токенов

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

Допустим у вас есть следующий REST-контроллер:

@RestController
public class GreetingController {

    @GetMapping("/greeting/hello")
    public String hello() {
        return "Hello!";
    }

    @PostMapping("/greeting/hi")
    public String hi() {
        return "Hi!";
    }
}

Чтобы сделать эндпойнты /greeting/** защищенными с помощью токена доступа, вам нужно объявить бин, реализующий интерфейс io.jmix.core.security.AuthorizedUrlsProvider, и возвращать список шаблонов URL из метода getAuthenticatedUrlPatterns():

@Component
public class GreetingAuthorizedUrlsProvider implements AuthorizedUrlsProvider {

    @Override
    public Collection<String> getAuthenticatedUrlPatterns() {
        return List.of("/greeting/**");
    }

    @Override
    public Collection<String> getAnonymousUrlPatterns() {
        return List.of();
    }
}

После создания такой конфигурации все запросы к эндпойнтам /greeting/** будут требовать токена доступа в заголовке Authorization. Например:

GET /greeting/hello HTTP/1.1
Host: server.example.com
Authorization: Bearer <ACCESS_TOKEN>

Устранение проблем

Если вы столкнулись с HTTP-ошибкой 401 Unauthorized или 403 Forbidden или с какими-либо другими проблемами, связанными с безопасностью эндпойнтов, то включите логгирование Spring Security. Для этого добавьте следующее свойство с значением DEBUG или TRACE в файл application.properties:

logging.level.org.springframework.security = TRACE