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

Запросы к приложению 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, созданный этой конфигурацией, всегда имеет наименьший приоритет и всегда вызывается последним. Секция Безопасность кастомных эндпойнтов ниже объясняет, как определить ваш собственный SecurityFilterChain для защиты пользовательских эндпойнтов.

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

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

Для определения кастомных правил безопасности для эндпойнтов объявите новый бин 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 {

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

    @GetMapping("/greeting/public/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.

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

Вы можете защитить пользовательские эндпойнты с помощью токенов доступа (bearer tokens), выданных сервером авторизации или внешним провайдером идентификации, таким как Keycloak, при использовании дополнения OpenID Connect. Конфигурации безопасности дополнений сервера авторизации и OpenID Connect предоставляют точки расширения для этой цели. В контексте спецификации OAuth 2.1 ваше приложение, которое размещает защищенные эндпойнты, действует как сервер ресурсов.

Предположим, у вас есть REST-контроллер:

@RestController
public class GreetingController {

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

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

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

Свойства приложения

Самый простой способ определить, какие эндпойнты сервера ресурсов должны быть защищены, а какие должны иметь публичный доступ — это использование следующих свойств приложения:

# All endpoints that match the given pattern will require a bearer token
jmix.resource-server.authenticated-url-patterns = /greeting/**

# However, endpoints that match the following pattern will be accessible without a token
jmix.resource-server.anonymous-url-patterns = /greeting/public/**

Поставщики шаблонов URL

Еще один способ определить, какие эндпойнты нужно защищать с помощью аутентификации на основе токенов — это создать бин, реализующий интерфейс AuthenticatedUrlPatternsProvider, и вернуть список шаблонов URL из его метода getAuthenticatedUrlPatterns(). Такой подход более гибкий и позволяет задавать более сложные правила для защиты эндпойнтов.

@Component
public class GreetingAuthenticatedUrlPatternsProvider implements AuthenticatedUrlPatternsProvider {

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

}

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

@Component
public class GreetingAnonymousUrlPatternsProvider implements AnonymousUrlPatternsProvider {

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

}

Поставщик RequestMatcher

Свойства приложения и поставщики шаблонов URL позволяют определять правила защиты эндпойнтов, работающие с набором строк, содержащих шаблоны URL. Если вам нужны более сложные правила, чем просто шаблон URL, например, с учетом HTTP-метода, вы можете реализовать интерфейс AuthenticatedRequestMatcherProvider и вернуть объект RequestMatcher из его метода getAuthenticatedRequestMatcher().

@Component
public class GreetingAuthenticatedRequestMatcherProvider implements AuthenticatedRequestMatcherProvider {

    @Override
    public RequestMatcher getAuthenticatedRequestMatcher() {
        return new AntPathRequestMatcher("/greeting/**", HttpMethod.POST.name());
    }
}

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

@Component
public class GreetingAnonymousRequestMatcherProvider implements AnonymousRequestMatcherProvider {

    @Override
    public RequestMatcher getAnonymousRequestMatcher() {
        return new AntPathRequestMatcher("/greeting/public/**", HttpMethod.GET.name());
    }
}

Доступ к эндпойнтам сервера ресурсов

После того как определены любые из вышеупомянутых конфигураций, все запросы к защищенным эндпойнтам /greeting/** сервера ресурсов будут требовать токен доступа (bearer token) в заголовке 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