Кастомные эндпойнты
Запросы к приложению 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