Веб
Spring Boot хорошо подходит для разработки веб-приложений. Вы можете создать автономный HTTP-сервер, используя встроенные Tomcat, Jetty, Undertow или Netty. Большинство веб-приложений используют модуль spring-boot-starter-web для быстрого запуска. Вы также можете выбрать разработку реактивных веб-приложений, используя модуль spring-boot-starter-webflux.
Если вы ещё не разрабатывали веб-приложение Spring Boot, вы можете следовать примеру "Hello World!" в разделе Начало работы.
1. Веб-приложения на основе Servlet
Если вы хотите создать веб-приложения на основе сервлетов, вы можете воспользоваться автоматической настройкой Spring Boot для Spring MVC или Jersey.
1.1. «Веб-фреймворк Spring MVC»
Фреймворк Spring Web MVC (часто называемый «Spring MVC») — это богатый фреймворк веб-приложений с архитектурой «модель-представление-контроллер». Spring MVC позволяет создавать специальные @Controller или @RestController бин для обработки входящих HTTP-запросов. Методы вашего контроллера сопоставляются с HTTP-запросами с помощью аннотаций @RequestMapping.
Следующий код демонстрирует типичный @RestController для предоставления данных в формате JSON:
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public User getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId).get();
}
@GetMapping("/{userId}/customers")
public List<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
}
@DeleteMapping("/{userId}")
public void deleteUser(@PathVariable Long userId) {
this.userRepository.deleteById(userId);
}
}
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): User {
return userRepository.findById(userId).get()
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): List<Customer> {
return userRepository.findById(userId).map(customerRepository::findByUser).get()
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long) {
userRepository.deleteById(userId)
}
}
Функциональный вариант «WebMvc.fn» разделяет конфигурацию маршрутизации и обработку запросов, как показано в следующем примере:
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun routerFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build()
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
@Component
public class MyUserHandler {
public ServerResponse getUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse getUserCustomers(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse deleteUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
}
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
fun getUserCustomers(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
fun deleteUser(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
}
Spring MVC является частью основного фреймворка Spring, и подробная информация доступна в документации. Также доступны несколько руководств по Spring MVC на сайте spring.io/guides.
Вы можете определять сколько угодно RouterFunction бинов для модульной организации маршрутизатора. Бин можно упорядочить, если требуется приоритет. |
1.1.1. Автоматическая настройка Spring MVC
Spring Boot предоставляет автоматическую настройку для Spring MVC, которая хорошо работает с большинством приложений. Она устраняет необходимость в @EnableWebMvc и их нельзя использовать вместе. В дополнение к стандартным настройкам Spring MVC, автоматическая настройка предоставляет следующие функции:
-
Включение
ContentNegotiatingViewResolverиBeanNameViewResolverбинов. -
Поддержка предоставления статических ресурсов, включая поддержку WebJars (подробнее будет описано позже в этом документе).
-
Автоматическая регистрация
Converter,GenericConverter, иFormatterбинов. -
Поддержка
HttpMessageConverters(подробнее будет описано позже в этом документе). -
Автоматическая регистрация
MessageCodesResolver(подробнее будет описано позже в этом документе). -
Поддержка статических
index.html. -
Автоматическое использование
ConfigurableWebBindingInitializerбина (подробнее будет описано позже в этом документе).
Если вы хотите сохранить настройки Spring Boot MVC и внести дополнительные настройки MVC (перехватчики, форматировщики, контроллеры представлений и другие функции), вы можете добавить свой собственный @Configuration класса WebMvcConfigurer, но **без** @EnableWebMvc.
Если вы хотите предоставить пользовательские экземпляры RequestMappingHandlerMapping, RequestMappingHandlerAdapter, или ExceptionHandlerExceptionResolver, и при этом сохранить настройки Spring Boot MVC, вы можете объявить бин типа WebMvcRegistrations и использовать его для предоставления пользовательских экземпляров этих компонентов.
Если вы не хотите использовать автоматическую настройку и хотите полностью контролировать Spring MVC, добавьте свой собственный бин, помеченный аннотацией @Configuration с аннотацией @EnableWebMvc. В качестве альтернативы добавьте свой собственный бин, помеченный аннотацией @Configuration, как описано в Javadoc @EnableWebMvc.
1.1.2. Сервис преобразования Spring MVC
Spring MVC использует другой ConversionService для преобразования значений из вашего файла application.properties или application.yaml. Это означает, что Period, Duration и DataSize преобразователи недоступны, и аннотации @DurationUnit и @DataSizeUnit будут проигнорированы.
Если вы хотите настроить ConversionService, используемый Spring MVC, вы можете предоставить бин WebMvcConfigurer с методом addFormatters. В этом методе вы можете зарегистрировать любые преобразователи или делегировать статическим методам, доступным в ApplicationConversionService.
Преобразование также можно настроить с помощью свойств конфигурации spring.mvc.format.*. Если они не настроены, используются следующие значения по умолчанию:
| Свойство | DateTimeFormatter |
|---|---|
|
|
|
|
|
|
1.1.3. HttpMessageConverters
Spring MVC использует интерфейс HttpMessageConverter для преобразования HTTP-запросов и ответов. По умолчанию включены разумные преобразователи. Например, объекты могут быть автоматически преобразованы в JSON (с использованием библиотеки Jackson) или XML (с использованием расширения Jackson XML, если оно доступно, или JAXB, если расширение Jackson XML недоступно). По умолчанию строки кодируются в UTF-8.
Если вам нужно добавить или настроить преобразователи, вы можете использовать класс Spring Boot HttpMessageConverters, как показано в следующем примере:
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
return new HttpMessageConverters(additional, another);
}
}
@Configuration(proxyBeanMethods = false)
class MyHttpMessageConvertersConfiguration {
@Bean
fun customConverters(): HttpMessageConverters {
val additional: HttpMessageConverter<*> = AdditionalHttpMessageConverter()
val another: HttpMessageConverter<*> = AnotherHttpMessageConverter()
return HttpMessageConverters(additional, another)
}
}
Любой HttpMessageConverter бин, присутствующий в контексте, добавляется в список преобразователей. Также можно переопределить преобразователи по умолчанию аналогичным образом.
1.1.4. MessageCodesResolver
Spring MVC имеет стратегию для генерации кодов ошибок для отображения сообщений об ошибках из ошибок привязки: MessageCodesResolver. Если вы установите свойство spring.mvc.message-codes-resolver-format в PREFIX_ERROR_CODE или POSTFIX_ERROR_CODE, Spring Boot создаст его за вас (см. перечисление в DefaultMessageCodesResolver.Format).
1.1.5. Статический контент
По умолчанию, Spring Boot подаёт статический контент из каталога, называемого /static (или /public или /resources или /META-INF/resources) в классе или из корня ServletContext. Он использует ResourceHttpRequestHandler из Spring MVC, позволяя изменить это поведение, добавив собственный WebMvcConfigurer и переопределив метод addResourceHandlers.
В автономном веб-приложении, по умолчанию сервлет контейнера не включён. Его можно включить, используя свойство server.servlet.register-default-servlet.
Сервлет по умолчанию выполняет функцию обратной связи, предоставляя контент из корня ServletContext, если Spring не обрабатывает его. В большинстве случаев этого не происходит (если вы не изменяете конфигурацию MVC по умолчанию), так как Spring всегда может обработать запросы через DispatcherServlet.
По умолчанию, ресурсы отображаются по /**, но вы можете настроить это с помощью свойства spring.mvc.static-path-pattern. Например, перемещение всех ресурсов в /resources/** можно сделать следующим образом:
spring.mvc.static-path-pattern=/resources/** spring:
mvc:
static-path-pattern: "/resources/**" Также можно настроить расположения статических ресурсов, используя свойство spring.web.resources.static-locations (заменив значения по умолчанию списком каталогов). Корневой путь контекста сервлета, "/", автоматически добавляется в качестве расположения.
В дополнение к «стандартным» расположениям статических ресурсов, упомянутым ранее, существует специальный случай для контента Webjars. По умолчанию, любые ресурсы с путём в /webjars/** подаются из файлов jar, если они упакованы в формате Webjars. Путь можно настроить с помощью свойства spring.mvc.webjars-path-pattern.
Не используйте каталог src/main/webapp если ваше приложение упаковано как jar. Хотя этот каталог является стандартным, он работает только с упаковкой war и безмолвно игнорируется большинством инструментов сборки при генерации jar. |
Spring Boot также поддерживает расширенные возможности обработки ресурсов, предоставляемые Spring MVC, что позволяет использовать такие случаи, как кэширование статических ресурсов или использование адресов URL, не зависящих от версии, для Webjars.
Чтобы использовать адреса URL, не зависящие от версии, для Webjars, добавьте зависимость webjars-locator-core. Затем объявите свой Webjar. Пример с jQuery: добавление "/webjars/jquery/jquery.min.js" приводит к "/webjars/jquery/x.y.z/jquery.min.js", где x.y.z — версия Webjar.
Если вы используете JBoss, вам необходимо объявить зависимость webjars-locator-jboss-vfs вместо webjars-locator-core. В противном случае все Webjars будут разрешены как 404. |
Для использования кэширования с помощью хеширования, следующая конфигурация настраивает решение для кэширования всех статических ресурсов, фактически добавляя хеш контента, например, <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>, в URL:
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/** spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**" Ссылки на ресурсы переписываются в шаблонах во время выполнения, благодаря ResourceUrlEncodingFilter , который автоматически настроен для Thymeleaf и FreeMarker. При использовании JSP вы должны вручную объявить этот фильтр. Другие движки шаблонов в настоящее время не поддерживаются автоматически, но могут быть с помощью пользовательских макросов/помощников шаблонов и использования ResourceUrlProvider. |
При динамической загрузке ресурсов, например, с помощью загрузчика модулей JavaScript, переименование файлов не является вариантом. Поэтому поддерживаются и могут комбинироваться другие стратегии. Стратегия "фиксированного" добавления статической строки версии в URL без изменения имени файла, показана в следующем примере:
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/js/lib/
spring.web.resources.chain.strategy.fixed.version=v12 spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
fixed:
enabled: true
paths: "/js/lib/"
version: "v12" С этой конфигурацией, модули JavaScript, расположенные в "/js/lib/", используют стратегию фиксированной версии ("/v12/js/lib/mymodule.js"), в то время как другие ресурсы по-прежнему используют стратегию хеширования (<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>).
Дополнительные поддерживаемые варианты см. в WebProperties.Resources.
| Эта функция подробно описана в отдельной статье блога и в справочной документации Spring Framework здесь. |
1.1.6. Страница приветствия
Spring Boot поддерживает как статические, так и шаблонные страницы приветствия. Сначала ищется файл index.html в заданных расположениях статического контента. Если он не найден, затем ищется шаблон index. Если любой из них найден, он автоматически используется как страница приветствия приложения.
1.1.7. Настройка Favicon
Как и другие статические ресурсы, Spring Boot проверяет наличие favicon.ico в заданных расположениях статического контента. Если такой файл есть, он автоматически используется в качестве favicon приложения.
1.1.8. Сопоставление путей и переговорка контента
Spring MVC может сопоставлять входящие HTTP-запросы с обработчиками, анализируя путь запроса и сопоставляя его с отображениями, определенными в приложении (например, аннотации @GetMapping на методах контроллера).
Spring Boot по умолчанию отключает сопоставление путей с суффиксами, что означает, что запросы, такие как "GET /projects/spring-boot.json", не будут сопоставлены с отображениями @GetMapping("/projects/spring-boot"). Это считается лучшей практикой для приложений Spring MVC. Эта функция была в основном полезна в прошлом для HTTP-клиентов, которые не отправляли корректные заголовки "Accept"; нам нужно было убедиться, что отправляем соответствующий тип контента клиенту. В настоящее время переговорка контента гораздо надежнее.
Существуют другие способы обработки HTTP-клиентов, которые не отправляют последовательно корректные заголовки "Accept". Вместо использования сопоставления по суффиксу, мы можем использовать параметр запроса, чтобы гарантировать, что запросы, такие как "GET /projects/spring-boot?format=json", будут сопоставлены с @GetMapping("/projects/spring-boot").
spring.mvc.contentnegotiation.favor-parameter=true spring:
mvc:
contentnegotiation:
favor-parameter: true Или, если вы предпочитаете использовать другое имя параметра:
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam spring:
mvc:
contentnegotiation:
favor-parameter: true
parameter-name: "myparam" Большинство стандартных типов медиа поддерживаются из коробки, но вы также можете определить новые:
spring.mvc.contentnegotiation.media-types.markdown=text/markdown spring:
mvc:
contentnegotiation:
media-types:
markdown: "text/markdown" Начиная с Spring Framework 5.3, Spring MVC поддерживает две стратегии для сопоставления путей запросов с контроллерами. По умолчанию Spring Boot использует стратегию PathPatternParser. PathPatternParser — это оптимизированная реализация, но она имеет некоторые ограничения по сравнению со стратегией AntPathMatcher. PathPatternParser ограничивает использование некоторых вариантов шаблонов путей. Она также несовместима с настройкой DispatcherServlet с префиксом пути (spring.mvc.servlet.path).
Стратегию можно настроить с помощью свойства конфигурации spring.mvc.pathmatch.matching-strategy, как показано в следующем примере:
spring.mvc.pathmatch.matching-strategy=ant-path-matcher spring:
mvc:
pathmatch:
matching-strategy: "ant-path-matcher" По умолчанию, Spring MVC отправит ошибку 404 Not Found, если обработчик не найден для запроса. Чтобы вместо этого была выброшена ошибка NoHandlerFoundException, установите configprop:spring.mvc.throw-exception-if-no-handler-found в true. Обратите внимание, что по умолчанию обработка статического контента отображается по /** и, следовательно, предоставит обработчик для всех запросов. Чтобы была выброшена ошибка NoHandlerFoundException, вам также необходимо установить spring.mvc.static-path-pattern на более конкретное значение, например, /resources/**, или установить spring.web.resources.add-mappings в false для отключения обработки статического контента.
1.1.9. ConfigurableWebBindingInitializer
Spring MVC использует WebBindingInitializer для инициализации WebDataBinder для конкретного запроса. Если вы создаёте собственный ConfigurableWebBindingInitializer @Bean, Spring Boot автоматически настраивает Spring MVC для его использования.
1.1.10. Шаблонизаторы
Помимо веб-служб REST, вы также можете использовать Spring MVC для предоставления динамического контента HTML. Spring MVC поддерживает различные технологии шаблонизации, включая Thymeleaf, FreeMarker и JSP. Кроме того, многие другие шаблонизаторы включают собственные интеграции с Spring MVC.
Spring Boot включает поддержку автоматической конфигурации для следующих шаблонизаторов:
| Если возможно, следует избегать использования JSP. Существует несколько известных ограничений при их использовании с встроенными контейнерами сервлетов. |
При использовании одного из этих шаблонизаторов с конфигурацией по умолчанию, шаблоны автоматически подбираются из src/main/resources/templates.
| В зависимости от способа запуска приложения, ваш IDE может упорядочивать пути к классам по-разному. Запуск приложения в IDE из метода main приводит к другому порядку, чем при запуске с помощью Maven, Gradle или из упакованного jar-файла. Это может привести к тому, что Spring Boot не сможет найти ожидаемые шаблоны. Если у вас возникла такая проблема, вы можете изменить порядок расположения в IDE, поместив классы и ресурсы модуля первыми. |
1.1.11. Обработка ошибок
По умолчанию Spring Boot предоставляет /error сопоставление, которое обрабатывает все ошибки осмысленным образом и регистрируется как «глобальная» страница ошибок в контейнере сервлетов. Для машинных клиентов он генерирует JSON-ответ с подробными сведениями об ошибке, HTTP-статусе и сообщении об исключении. Для клиентов браузера есть «белая» страница ошибок, которая отображает те же данные в формате HTML (чтобы настроить её, добавьте View , который указывает на error).
Существует ряд server.error свойств, которые можно задать, если вы хотите настроить поведение обработки ошибок по умолчанию. См. раздел «Свойства сервера» в Приложении.
Чтобы полностью заменить поведение по умолчанию, вы можете реализовать ErrorController и зарегистрировать определение бин этого типа или добавить бин типа ErrorAttributes для использования существующей механики, но замены содержимого.
BasicErrorController может быть использован в качестве базового класса для пользовательского ErrorController. Это особенно полезно, если вы хотите добавить обработчик для нового типа контента (по умолчанию обрабатывается text/html и предоставляется резервный вариант для всего остального). Для этого расширьте BasicErrorController, добавьте публичный метод с @RequestMapping , имеющим produces атрибут и создайте бин вашего нового типа. |
Начиная со Spring Framework 6.0, поддерживаются детали проблем RFC 7807. Spring MVC может генерировать пользовательские сообщения об ошибках с application/problem+json медиа-типом, например:
{
"type": "https://example.org/problems/unknown-project",
"title": "Unknown project",
"status": 404,
"detail": "No project found for id 'spring-unknown'",
"instance": "/projects/spring-unknown"
} Эта поддержка может быть включена путём задания spring.mvc.problemdetails.enabled в значение true.
Вы также можете определить класс, аннотированный @ControllerAdvice, для настройки JSON-документа, возвращаемого для определённого контроллера и/или типа исключения, как показано в следующем примере:
@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
@ResponseBody
@ExceptionHandler(MyException.class)
public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
HttpStatus status = HttpStatus.resolve(code);
return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
}
}
@ControllerAdvice(basePackageClasses = [SomeController::class])
class MyControllerAdvice : ResponseEntityExceptionHandler() {
@ResponseBody
@ExceptionHandler(MyException::class)
fun handleControllerException(request: HttpServletRequest, ex: Throwable): ResponseEntity<*> {
val status = getStatus(request)
return ResponseEntity(MyErrorBody(status.value(), ex.message), status)
}
private fun getStatus(request: HttpServletRequest): HttpStatus {
val code = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE) as Int
val status = HttpStatus.resolve(code)
return status ?: HttpStatus.INTERNAL_SERVER_ERROR
}
}
В приведённом выше примере, если MyException выбрасывается контроллером, определённым в том же пакете, что и SomeController, будет использоваться JSON-представление MyErrorBody POJO вместо ErrorAttributes представления.
В некоторых случаях ошибки, обрабатываемые на уровне контроллера, не записываются в инфраструктуру метрики. Приложения могут обеспечить запись таких исключений в метрики запроса, установив обработанное исключение как атрибут запроса:
@Controller
public class MyController {
@ExceptionHandler(CustomException.class)
String handleCustomException(HttpServletRequest request, CustomException ex) {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
return "errorView";
}
}
@Controller
class MyController {
@ExceptionHandler(CustomException::class)
fun handleCustomException(request: HttpServletRequest, ex: CustomException?): String {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex)
return "errorView"
}
}
Настраиваемые страницы ошибок
Если вы хотите отобразить пользовательскую страницу ошибки HTML для определённого кода состояния, вы можете добавить файл в /error каталог. Страницы ошибок могут быть статичным HTML (то есть, добавлены в любом из каталогов статических ресурсов) или построены с использованием шаблонов. Имя файла должно соответствовать точному коду состояния или маске серии.
Например, для сопоставления 404 со статическим HTML-файлом структура вашего каталога будет следующей:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets> Для сопоставления всех 5xx ошибок с помощью шаблона FreeMarker структура каталога будет следующей:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftlh
+- <other templates> Для более сложных сопоставлений вы также можете добавить бины, реализующие интерфейс ErrorViewResolver, как показано в следующем примере:
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
new ModelAndView("myview");
}
return null;
}
}
class MyErrorViewResolver : ErrorViewResolver {
override fun resolveErrorView(request: HttpServletRequest, status: HttpStatus,
model: Map<String, Any>): ModelAndView? {
// Use the request or status to optionally return a ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
return ModelAndView("myview")
}
return null
}
}
Вы также можете использовать обычные функции Spring MVC, такие как @ExceptionHandler методы и @ControllerAdvice. ErrorController затем обрабатывает любые необработанные исключения.
Сопоставление страниц ошибок вне Spring MVC
Для приложений, не использующих Spring MVC, вы можете использовать интерфейс ErrorPageRegistrar для прямой регистрации ErrorPages. Эта абстракция работает непосредственно с базовым встроенным контейнером сервлетов и работает даже если у вас нет Spring MVC DispatcherServlet.
@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return this::registerErrorPages;
}
private void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}
@Configuration(proxyBeanMethods = false)
class MyErrorPagesConfiguration {
@Bean
fun errorPageRegistrar(): ErrorPageRegistrar {
return ErrorPageRegistrar { registry: ErrorPageRegistry -> registerErrorPages(registry) }
}
private fun registerErrorPages(registry: ErrorPageRegistry) {
registry.addErrorPages(ErrorPage(HttpStatus.BAD_REQUEST, "/400"))
}
}
Если вы регистрируете ErrorPage с путём, который в конечном итоге обрабатывается Filter (как это обычно бывает с некоторыми не-Spring веб-фреймворками, такими как Jersey и Wicket), то Filter должен быть явно зарегистрирован как ERROR диспатчер, как показано в следующем примере: |
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
}
@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {
@Bean
fun myFilter(): FilterRegistrationBean<MyFilter> {
val registration = FilterRegistrationBean(MyFilter())
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType::class.java))
return registration
}
}
Обратите внимание, что FilterRegistrationBean по умолчанию не включает ERROR тип диспатчера.
Обработка ошибок при развертывании в WAR
При развертывании в контейнер сервлетов Spring Boot использует свой фильтр страниц ошибок для перенаправления запроса с кодом состояния ошибки на соответствующую страницу ошибки. Это необходимо, так как спецификация сервлетов не предоставляет API для регистрации страниц ошибок. В зависимости от контейнера, в который вы развертываете свой WAR-файл, и технологий, используемых вашим приложением, могут потребоваться дополнительные настройки.
Фильтр страниц ошибок может перенаправить запрос только на правильную страницу ошибки, если ответ ещё не отправлен. По умолчанию WebSphere Application Server 8.0 и более поздние версии отправляют ответ при успешном выполнении метода service сервлета. Вы должны отключить это поведение, установив com.ibm.ws.webcontainer.invokeFlushAfterService в значение false.
1.1.12. Поддержка CORS
Обмен ресурсами из разных доменов (CORS) — это спецификация W3C, реализованная в большинстве браузеров, которая позволяет гибко определять, какие запросы из разных доменов разрешены, вместо использования менее безопасных и менее мощных методов, таких как IFRAME или JSONP.
Начиная с версии 4.2, Spring MVC поддерживает CORS. Использование конфигурации CORS в методах контроллера с @CrossOrigin аннотациями в вашем приложении Spring Boot не требует никакой специальной конфигурации. Глобальную конфигурацию CORS можно определить, зарегистрировав WebMvcConfigurer бины с настраиваемым методом addCorsMappings(CorsRegistry), как показано в следующем примере:
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
}
}
}
}
1.2. JAX-RS и Jersey
Если вы предпочитаете модель программирования JAX-RS для REST-точек входа, вы можете использовать одно из доступных реализаций вместо Spring MVC. Jersey и Apache CXF работают достаточно хорошо «из коробки». CXF требует регистрации его Servlet или Filter как @Bean в контексте вашего приложения. Jersey имеет некоторую родную поддержку Spring, поэтому мы также предоставляем поддержку автоматической конфигурации для него в Spring Boot вместе с стартером.
Чтобы начать работу с Jersey, включите spring-boot-starter-jersey в качестве зависимости, а затем вам нужен один @Bean типа ResourceConfig, в котором вы регистрируете все точки входа, как показано в следующем примере:
@Component
public class MyJerseyConfig extends ResourceConfig {
public MyJerseyConfig() {
register(MyEndpoint.class);
}
}
Поддержка Jersey для сканирования исполняемых архивов довольно ограничена. Например, он не может отсканировать точки входа в пакете, найденном в полностью исполняемом файле jar или в WEB-INF/classes при запуске исполняемого файла war. Чтобы избежать этого ограничения, метод packages не должен использоваться, и точки входа должны регистрироваться индивидуально с помощью метода register, как показано в предыдущем примере. |
Для более продвинутых настроек вы также можете зарегистрировать произвольное количество бинов, реализующих ResourceConfigCustomizer.
Все зарегистрированные точки входа должны быть @Components с аннотациями HTTP-ресурсов (@GET и другие), как показано в следующем примере:
@Component
@Path("/hello")
public class MyEndpoint {
@GET
public String message() {
return "Hello";
}
}
Так как Endpoint является Spring @Component, его жизненный цикл управляется Spring, и вы можете использовать аннотацию @Autowired для инъекции зависимостей и аннотацию @Value для инъекции внешней конфигурации. По умолчанию сервлет Jersey регистрируется и отображается на /*. Вы можете изменить отображение, добавив @ApplicationPath в ваш ResourceConfig.
По умолчанию Jersey настроен как сервлет в @Bean типа ServletRegistrationBean с именем jerseyServletRegistration. По умолчанию сервлет инициализируется лениво, но вы можете настроить это поведение, установив spring.jersey.servlet.load-on-startup. Вы можете отключить или переопределить этот бины, создав свой собственный с тем же именем. Вы также можете использовать фильтр вместо сервлета, установив spring.jersey.type=filter (в этом случае @Bean для замены или переопределения — jerseyFilterRegistration). Фильтр имеет @Order, которое можно задать с помощью spring.jersey.filter.order. При использовании Jersey в качестве фильтра должен присутствовать сервлет, который будет обрабатывать все запросы, которые не перехватываются Jersey. Если ваше приложение не содержит такого сервлета, вы можете включить сервлет по умолчанию, установив server.servlet.register-default-servlet на true. И сервлет, и регистрации фильтров могут быть заданы начальные параметры, используя spring.jersey.init.* для указания карты свойств.
1.3. Поддержка встроенного контейнера сервлетов
Для приложений сервлетов Spring Boot включает поддержку встроенных серверов Tomcat, Jetty и Undertow. Большинство разработчиков используют соответствующий «Стартер» для получения полностью настроенного экземпляра. По умолчанию встроенный сервер прослушивает HTTP-запросы на порту 8080.
1.3.1. Сервлеты, фильтры и слушатели
При использовании встроенного контейнера сервлетов вы можете регистрировать сервлеты, фильтры и все слушатели (например, HttpSessionListener) из спецификации сервлетов, используя либо Spring-бин или сканируя компоненты сервлетов.
Регистрация сервлетов, фильтров и слушателей в качестве Spring-бинов
Любой Servlet, Filter, или экземпляр сервлета *Listener, являющийся Spring-бином, регистрируется в встроенном контейнере. Это может быть особенно удобно, если вам нужно обратиться к значению из вашего application.properties во время конфигурации.
По умолчанию, если контекст содержит только один сервлет, он отображается по адресу /. В случае нескольких сервлет-бинов имя бин используется в качестве префикса пути. Фильтры отображаются по адресу /*.
Если конвенциональная настройка отображения недостаточно гибкая, вы можете использовать классы ServletRegistrationBean, FilterRegistrationBean и ServletListenerRegistrationBean для полного управления.
Обычно безопасно оставлять бин-фильтры без порядка. Если необходим определенный порядок, вы должны аннотировать Filter с помощью @Order или заставить его реализовать Ordered. Вы не можете настроить порядок Filter путем аннотации метода бин с @Order. Если вы не можете изменить класс Filter для добавления @Order или реализации Ordered, вы должны определить FilterRegistrationBean для Filter и установить порядок бин-регистрации, используя метод setOrder(int). Избегайте конфигурирования фильтра, который считывает тело запроса в Ordered.HIGHEST_PRECEDENCE, так как это может противоречить настройкам кодировки символов вашего приложения. Если фильтр сервлета оборачивает запрос, он должен быть сконфигурирован с порядком, который меньше или равен OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER.
Чтобы увидеть порядок каждого Filter в вашем приложении, включите отладку уровня логирования для web группы логирования (logging.level.web=debug). Тогда детали зарегистрированных фильтров, включая их порядок и шаблоны URL, будут записаны при запуске. |
Будьте внимательны при регистрации Filter бинов, так как они инициализируются очень рано в жизненном цикле приложения. Если вам нужно зарегистрировать Filter , который взаимодействует с другими бинaми, рассмотрите использование DelegatingFilterProxyRegistrationBean вместо этого. |
1.3.2. Инициализация контекста сервлета
Встроенные контейнеры сервлетов не выполняют напрямую интерфейс jakarta.servlet.ServletContainerInitializer или интерфейс Spring org.springframework.web.WebApplicationInitializer. Это умышленное конструкторское решение, призванное снизить риск того, что сторонние библиотеки, предназначенные для работы внутри war, могут сломать приложения Spring Boot.
Если вам нужно выполнить инициализацию контекста сервлета в приложении Spring Boot, вы должны зарегистрировать бин, который реализует интерфейс org.springframework.boot.web.servlet.ServletContextInitializer. Единственный метод onStartup предоставляет доступ к ServletContext и, при необходимости, может легко использоваться в качестве адаптера к существующему WebApplicationInitializer.
Сканирование сервлетов, фильтров и слушателей
При использовании встроенного контейнера автоматическая регистрация классов, аннотированных @WebServlet, @WebFilter и @WebListener, может быть включена с помощью @ServletComponentScan.
@ServletComponentScan не оказывает влияния в автономном контейнере, где вместо этого используются встроенные механизмы обнаружения контейнера. |
1.3.3. ServletWebServerApplicationContext
Внутри, Spring Boot использует другой тип ApplicationContext для поддержки встроенного контейнера сервлетов. ServletWebServerApplicationContext — это специальный тип WebApplicationContext, который задействует себя, ища один ServletWebServerFactory бин. Обычно TomcatServletWebServerFactory, JettyServletWebServerFactory или UndertowServletWebServerFactory был автоматически сконфигурирован.
Обычно вам не нужно знать об этих классах реализации. Большинство приложений автоматически настраиваются, и соответствующие ApplicationContext и ServletWebServerFactory создаются от вашего имени. |
В настройке с встроенным контейнером ServletContext устанавливается в рамках запуска сервера, который происходит во время инициализации контекста приложения. Из-за этого бины в ApplicationContext не могут быть надежно инициализированы с помощью ServletContext. Один из способов обойти это — ввести ApplicationContext в качестве зависимости бина и получить доступ к ServletContext только тогда, когда это необходимо. Другой способ — использовать обратный вызов после запуска сервера. Это можно сделать с помощью ApplicationListener, который прослушивает ApplicationStartedEvent, как показано ниже:
public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {
private ServletContext servletContext;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
}
}
1.3.4. Настройка встроенных контейнеров сервлетов
Общие настройки контейнера сервлетов можно настроить, используя свойства Spring Environment. Обычно вы определяете свойства в файле application.properties или application.yaml.
Общие настройки сервера включают:
-
Настройки сети: порт прослушивания входящих HTTP-запросов (
server.port), адрес интерфейса для привязкиserver.address, и так далее. -
Настройки сессий: является ли сессия постоянной (
server.servlet.session.persistent), время ожидания сессии (server.servlet.session.timeout), местоположение данных сессии (server.servlet.session.store-dir), и конфигурация cookie сессии (server.servlet.session.cookie.*). -
Управление ошибками: расположение страницы ошибок (
server.error.path) и так далее.
Spring Boot старается максимально раскрыть общие настройки, но это не всегда возможно. В таких случаях, специализированные пространства имён предлагают настройки, специфичные для сервера (см. server.tomcat и server.undertow). Например, логи доступа можно настроить с помощью специфических функций встроенного контейнера сервлетов.
Смотрите класс ServerProperties для полного списка. |
SameSite Cookie
Атрибут cookie SameSite может использоваться веб-браузерами для управления тем, отправляются ли и как cookies отправляются в запросах между сайтами. Этот атрибут особенно актуален для современных веб-браузеров, которые начали изменять значение по умолчанию, когда атрибут отсутствует.
Если вы хотите изменить атрибут SameSite вашего cookie сессии, вы можете использовать свойство server.servlet.session.cookie.same-site. Это свойство поддерживается автоматически настроенными серверами Tomcat, Jetty и Undertow. Оно также используется для настройки основанных на сервлетах компонентов SessionRepository Spring Session.
Например, если вы хотите, чтобы ваш cookie сессии имел атрибут SameSite со значением None, вы можете добавить следующее в файл application.properties или application.yaml:
server.servlet.session.cookie.same-site=none server:
servlet:
session:
cookie:
same-site: "none" Если вы хотите изменить атрибут SameSite других cookies, добавленных в ваш HttpServletResponse, вы можете использовать CookieSameSiteSupplier. CookieSameSiteSupplier получает Cookie и может возвратить значение SameSite, или null.
Существует множество удобных методов фабрики и фильтра, которые вы можете использовать для быстрого соответствия определенным cookies. Например, добавление следующего компонента автоматически применит SameSite со значением Lax для всех cookies с именем, соответствующим регулярному выражению myapp.*.
@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
@Bean
public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
}
}
@Configuration(proxyBeanMethods = false)
class MySameSiteConfiguration {
@Bean
fun applicationCookieSameSiteSupplier(): CookieSameSiteSupplier {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*")
}
}
Программная настройка
Если вам необходимо настроить встроенный контейнер сервлетов программно, вы можете зарегистрировать компонент Spring, реализующий интерфейс WebServerFactoryCustomizer. WebServerFactoryCustomizer предоставляет доступ к ConfigurableServletWebServerFactory, который включает множество методов-сеттеров для настройки. Следующий пример демонстрирует программную настройку порта:
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
override fun customize(server: ConfigurableServletWebServerFactory) {
server.setPort(9000)
}
}
TomcatServletWebServerFactory, JettyServletWebServerFactory и UndertowServletWebServerFactory – специализированные варианты ConfigurableServletWebServerFactory, имеющие дополнительные методы-сеттеры для настройки Tomcat, Jetty и Undertow соответственно. Следующий пример демонстрирует, как настроить TomcatServletWebServerFactory, который предоставляет доступ к настройкам, специфичным для Tomcat:
@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory server) {
server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
}
}
@Component
class MyTomcatWebServerFactoryCustomizer : WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
override fun customize(server: TomcatServletWebServerFactory) {
server.addConnectorCustomizers({ connector -> connector.asyncTimeout = Duration.ofSeconds(20).toMillis() })
}
}
Настройка ConfigurableServletWebServerFactory напрямую
Для более сложных случаев, требующих расширения от ServletWebServerFactory, вы можете сами экспонировать компонент такого типа.
Представлены сеттеры для многих опций конфигурации. Также доступны несколько защищённых методов «захвата», если вам нужно что-то более экзотическое. Смотрите документацию исходного кода для подробностей.
| Автоматически настроенные кастомайзеры всё ещё применяются к вашему пользовательскому фабрикату, поэтому используйте этот вариант с осторожностью. |
1.3.5. Ограничения JSP
При запуске приложения Spring Boot, использующего встроенный контейнер сервлетов (и упакованного как исполняемый архив), существуют некоторые ограничения в поддержке JSP.
-
С Jetty и Tomcat, всё должно работать, если вы используете упаковку war. Исполняемый war будет работать при запуске с
java -jar, и также будет развёртываемым в любом стандартном контейнере. JSP не поддерживаются при использовании исполняемого jar. -
Undertow не поддерживает JSP.
-
Создание пользовательской
error.jspстраницы не переопределяет страницу по умолчанию для обработки ошибок. Следует использовать пользовательские страницы ошибок вместо этого.
2. Реактивные веб-приложения
Spring Boot упрощает разработку реактивных веб-приложений, предоставляя автоматическую настройку для Spring Webflux.
2.1. «Фреймворк Spring WebFlux»
Spring WebFlux — это новый реактивный веб-фреймворк, представленный в Spring Framework 5.0. В отличие от Spring MVC, он не требует API сервлетов, полностью асинхронен и неблокирующий, а также реализует спецификацию Reactive Streams через проект Reactor.
Spring WebFlux существует в двух вариантах: функциональном и основанном на аннотациях. Вариант с аннотациями довольно близок к модели Spring MVC, как показано в следующем примере:
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public Mono<User> getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId);
}
@GetMapping("/{userId}/customers")
public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
}
@DeleteMapping("/{userId}")
public Mono<Void> deleteUser(@PathVariable Long userId) {
return this.userRepository.deleteById(userId);
}
}
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): Mono<User?> {
return userRepository.findById(userId)
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): Flux<Customer> {
return userRepository.findById(userId).flatMapMany { user: User? ->
customerRepository.findByUser(user)
}
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long): Mono<Void> {
return userRepository.deleteById(userId)
}
}
«WebFlux.fn», функциональный вариант, отделяет конфигурацию маршрутизации от фактической обработки запросов, как показано в следующем примере:
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun monoRouterFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route(
GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute(
GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute(
DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser)
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
@Component
public class MyUserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
...
}
}
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
}
WebFlux является частью Spring Framework, и подробная информация доступна в его документации.
Вы можете определить сколько угодно RouterFunction бинов для модулизации определения маршрутизатора. Бины могут быть отсортированы, если необходимо применить приоритет. |
Для начала добавьте spring-boot-starter-webflux модуль в своё приложение.
Добавление spring-boot-starter-web и spring-boot-starter-webflux модулей в ваше приложение приводит к автоматической настройке Spring Boot для Spring MVC, а не WebFlux. Такое поведение было выбрано, потому что многие разработчики Spring добавляют spring-boot-starter-webflux в свои приложения Spring MVC, чтобы использовать реактивные WebClient. Вы по-прежнему можете навязать свой выбор, установив выбранный тип приложения в SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE). |
2.1.1. Автоматическая настройка Spring WebFlux
Spring Boot предоставляет автоматическую настройку для Spring WebFlux, которая хорошо работает с большинством приложений.
Автоматическая настройка добавляет следующие функции поверх стандартных настроек Spring:
-
Настройка кодеров для
HttpMessageReaderиHttpMessageWriterэкземпляров (описано позже в этом документе). -
Поддержка предоставления статических ресурсов, включая поддержку WebJars (описано позже в этом документе).
Если вы хотите сохранить функции Spring Boot WebFlux и добавить дополнительную конфигурацию WebFlux, вы можете добавить свой собственный @Configuration класс типа WebFluxConfigurer, но без @EnableWebFlux.
Если вы хотите полностью контролировать Spring WebFlux, вы можете добавить свой собственный @Configuration аннотированный @EnableWebFlux.
2.1.2. Сервис преобразования Spring WebFlux
Если вы хотите настроить ConversionService, используемый Spring WebFlux, вы можете предоставить WebFluxConfigurer бин с методом addFormatters.
Преобразование также можно настроить, используя свойства конфигурации spring.webflux.format.*. Если не настроено, используются следующие значения по умолчанию:
| Свойство | DateTimeFormatter |
|---|---|
|
|
|
|
|
|
2.1.3. HTTP-кодеки с HttpMessageReaders и HttpMessageWriters
Spring WebFlux использует интерфейсы HttpMessageReader и HttpMessageWriter для преобразования HTTP-запросов и ответов. Они настраиваются с помощью CodecConfigurer для получения разумных значений по умолчанию, просматривая доступные в классе библиотеки.
Spring Boot предоставляет отдельные свойства конфигурации для кодеров, spring.codec.*. Он также применяет дополнительную настройку, используя экземпляры CodecCustomizer. Например, ключи конфигурации spring.jackson.* применяются к кодеру Jackson.
Если вам нужно добавить или настроить кодеров, вы можете создать пользовательский компонент CodecCustomizer, как показано в следующем примере:
@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {
@Bean
public CodecCustomizer myCodecCustomizer() {
return (configurer) -> {
configurer.registerDefaults(false);
configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
// ...
};
}
}
class MyCodecsConfiguration {
@Bean
fun myCodecCustomizer(): CodecCustomizer {
return CodecCustomizer { configurer: CodecConfigurer ->
configurer.registerDefaults(false)
configurer.customCodecs().register(ServerSentEventHttpMessageReader())
}
}
}
Вы также можете использовать настраиваемые сериализаторы и десериализаторы JSON Boot.
2.1.4. Статические ресурсы
По умолчанию Spring Boot предоставляет статические ресурсы из каталога, называемого /static (или /public или /resources или /META-INF/resources) в пути к классам. Он использует ResourceWebHandler из Spring WebFlux, поэтому вы можете изменить это поведение, добавив свой собственный WebFluxConfigurer и переопределив метод addResourceHandlers.
По умолчанию ресурсы отображаются в /**, но вы можете настроить это, установив свойство spring.webflux.static-path-pattern. Например, перемещение всех ресурсов в /resources/** можно сделать следующим образом:
spring.webflux.static-path-pattern=/resources/** spring:
webflux:
static-path-pattern: "/resources/**" Вы также можете настроить расположения статических ресурсов, используя spring.web.resources.static-locations. Это заменяет значения по умолчанию списком расположений каталогов. Если вы это сделаете, по умолчанию будет использоваться обнаружение стартовой страницы в ваших настроенных каталогах. Таким образом, если на старте есть index.html в любом из ваших каталогов, это будет домашней страницей приложения.
В дополнение к «стандартным» расположениям статических ресурсов, упомянутым ранее, имеется особый случай для Webjars-контента. По умолчанию любые ресурсы с путём в /webjars/** подаются из jar-файлов, если они упакованы в формате Webjars. Путь можно настроить с помощью свойства spring.webflux.webjars-path-pattern.
Приложения Spring WebFlux не зависят от API сервлетов, поэтому их нельзя развертывать как war-файлы и они не используют каталог src/main/webapp. |
2.1.5. Стартовая страница
Spring Boot поддерживает как статические, так и шаблонизированные стартовые страницы. Сначала он ищет файл index.html в настроенных расположениях статических ресурсов. Если он не найден, он затем ищет шаблон index. Если один из них найден, он автоматически используется в качестве стартовой страницы приложения.
2.1.6. Двигатели шаблонов
Помимо веб-сервисов REST, вы также можете использовать Spring WebFlux для предоставления динамического HTML-контента. Spring WebFlux поддерживает различные технологии шаблонизации, включая Thymeleaf, FreeMarker и Mustache.
Spring Boot включает поддержку автоматической настройки для следующих двигателей шаблонов:
При использовании одного из этих двигателей шаблонов с конфигурацией по умолчанию, шаблоны автоматически подбираются из src/main/resources/templates.
2.1.7. Обработка ошибок
Spring Boot предоставляет WebExceptionHandler , который обрабатывает все ошибки разумным способом. Его позиция в порядке обработки находится непосредственно перед обработчиками, предоставляемыми WebFlux, которые считаются последними. Для машинных клиентов он генерирует JSON-ответ с деталями ошибки, HTTP-статусом и сообщением об исключении. Для клиентов браузера существует обработчик ошибок «whitelabel», который отображает те же данные в формате HTML. Вы также можете предоставить свои собственные HTML-шаблоны для отображения ошибок (см. следующий раздел).
Прежде чем настраивать обработку ошибок в Spring Boot напрямую, вы можете использовать поддержку RFC 7807 Problem Details в Spring WebFlux. Spring WebFlux может генерировать пользовательские сообщения об ошибках с типом носителя application/problem+json, например:
{
"type": "https://example.org/problems/unknown-project",
"title": "Unknown project",
"status": 404,
"detail": "No project found for id 'spring-unknown'",
"instance": "/projects/spring-unknown"
} Эту поддержку можно включить, установив spring.webflux.problemdetails.enabled в значение true.
Первый шаг к настройке этой функции часто включает использование существующего механизма, но замену или дополнение содержимого ошибок. Для этого можно добавить бин типа ErrorAttributes.
Для изменения поведения обработки ошибок можно реализовать ErrorWebExceptionHandler и зарегистрировать определение бина этого типа. Поскольку ErrorWebExceptionHandler достаточно низкого уровня, Spring Boot также предоставляет удобный AbstractErrorWebExceptionHandler, чтобы вы могли обрабатывать ошибки функциональным способом WebFlux, как показано в следующем примере:
@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources,
ApplicationContext applicationContext) {
super(errorAttributes, resources, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
}
private boolean acceptsXml(ServerRequest request) {
return request.headers().accept().contains(MediaType.APPLICATION_XML);
}
public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
// ... additional builder calls
return builder.build();
}
}
@Component
class MyErrorWebExceptionHandler(errorAttributes: ErrorAttributes?, resources: WebProperties.Resources?,
applicationContext: ApplicationContext?) : AbstractErrorWebExceptionHandler(errorAttributes, resources, applicationContext) {
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
}
private fun acceptsXml(request: ServerRequest): Boolean {
return request.headers().accept().contains(MediaType.APPLICATION_XML)
}
fun handleErrorAsXml(request: ServerRequest?): Mono<ServerResponse> {
val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
// ... additional builder calls
return builder.build()
}
}
Для более полного представления вы также можете подклассировать DefaultErrorWebExceptionHandler напрямую и переопределить определенные методы.
В некоторых случаях ошибки, обрабатываемые на уровне контроллера или функции обработчика, не регистрируются инфраструктурой метрик метрики. Приложения могут гарантировать, что такие исключения регистрируются с метриками запроса, установив обработанное исключение в качестве атрибута запроса:
@Controller
public class MyExceptionHandlingController {
@GetMapping("/profile")
public Rendering userProfile() {
// ...
throw new IllegalStateException();
}
@ExceptionHandler(IllegalStateException.class)
public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) {
exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build();
}
}
@Controller
class MyExceptionHandlingController {
@GetMapping("/profile")
fun userProfile(): Rendering {
// ...
throw IllegalStateException()
}
@ExceptionHandler(IllegalStateException::class)
fun handleIllegalState(exchange: ServerWebExchange, exc: IllegalStateException): Rendering {
exchange.attributes.putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc)
return Rendering.view("errorView").modelAttribute("message", exc.message ?: "").build()
}
}
Настраиваемые страницы ошибок
Если вы хотите отобразить пользовательскую HTML-страницу ошибки для заданного кода состояния, вы можете добавить представления, которые разрешаются из error/*, например, добавив файлы в каталог /error. Страницы ошибок могут быть статичными HTML (то есть добавленными в любой из каталогов статических ресурсов) или созданными с помощью шаблонов. Название файла должно совпадать с точным кодом состояния, маской кода состояния или error для значения по умолчанию, если ничего больше не соответствует. Обратите внимание, что путь к представлению ошибки по умолчанию error/error, тогда как в Spring MVC значение по умолчанию error.
Например, для сопоставления 404 со статичным HTML-файлом структура вашего каталога будет следующей:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets> Для сопоставления всех 5xx ошибок с помощью шаблона Mustache структура вашего каталога будет следующей:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates> 2.1.8. Веб-фильтры
Spring WebFlux предоставляет интерфейс WebFilter, который можно реализовать для фильтрации обменов HTTP-запрос-ответ. WebFilter бины, найденные в контексте приложения, будут автоматически использоваться для фильтрации каждого обмена.
В тех случаях, когда порядок фильтров важен, они могут реализовывать Ordered или быть аннотированы @Order. Автоконфигурация Spring Boot может настроить веб-фильтры за вас. Когда это происходит, будут использоваться порядки, показанные в следующей таблице:
| Веб-фильтр | Порядок |
|---|---|
|
|
|
|
|
|
2.2. Встроенная поддержка реактивного сервера
Spring Boot включает поддержку следующих встроенных реактивных веб-серверов: Reactor Netty, Tomcat, Jetty и Undertow. Большинство разработчиков используют соответствующий «Starter», чтобы получить полностью настроенный экземпляр. По умолчанию встроенный сервер прослушивает HTTP-запросы на порту 8080.
2.3. Настройка ресурсов реактивного сервера
При автоматической конфигурации сервера Reactor Netty или Jetty Spring Boot создаст определенные бины, которые предоставят HTTP-ресурсы экземпляру сервера: ReactorResourceFactory или JettyResourceFactory.
По умолчанию эти ресурсы также будут использоваться клиентами Reactor Netty и Jetty для оптимальной производительности, учитывая:
-
используется одна и та же технология для сервера и клиента
-
экземпляр клиента создан с использованием
WebClient.Builderбин, автоматически настроенного Spring Boot
Разработчики могут переопределить конфигурацию ресурсов для Jetty и Reactor Netty, предоставив пользовательский ReactorResourceFactory или JettyResourceFactory бин — это будет применено к клиентам и серверам.
Вы можете узнать больше о настройке ресурсов на стороне клиента в разделе WebClient Runtime.
3. Плавное завершение
Плавное завершение поддерживается со всеми четырьмя встроенными веб-серверами (Jetty, Reactor Netty, Tomcat и Undertow) и с реактивными, и с основанными на сервлетах веб-приложениями. Оно происходит как часть закрытия контекста приложения и выполняется на самой ранней стадии остановки SmartLifecycle бинов. Эта остановка обработки использует таймаут, который предоставляет временной интервал, в течение которого существующим запросам будет разрешено завершиться, но новые запросы будут запрещены. Точный способ запрета новых запросов зависит от используемого веб-сервера. Jetty, Reactor Netty и Tomcat прекратят прием запросов на сетевом уровне. Undertow примет запросы, но немедленно ответит ответом «сервис недоступен» (503).
| Плавное завершение с Tomcat требует Tomcat 9.0.33 или более поздней версии. |
Для включения плавного завершения настройте свойство server.shutdown, как показано в следующем примере:
server.shutdown=graceful server:
shutdown: "graceful" Для настройки периода таймаута настройте свойство spring.lifecycle.timeout-per-shutdown-phase, как показано в следующем примере:
spring.lifecycle.timeout-per-shutdown-phase=20s spring:
lifecycle:
timeout-per-shutdown-phase: "20s" Использование плавного завершения с вашей IDE может работать неправильно, если она не отправляет правильный SIGTERM сигнал. Для получения дополнительной информации см. документацию вашей IDE. |
4. Безопасность Spring
Если Spring Security находится в пути к классам, то веб-приложения защищены по умолчанию. Spring Boot полагается на стратегию переключения содержимого Spring Security, чтобы определить, использовать ли httpBasic или formLogin. Чтобы добавить безопасность на уровне методов в веб-приложение, вы также можете добавить @EnableGlobalMethodSecurity с вашими настройками. Дополнительную информацию можно найти в Руководстве по Spring Security.
По умолчанию UserDetailsService имеет одного пользователя. Имя пользователя — user, а пароль — случайный и выводится на уровень WARN при запуске приложения, как показано в следующем примере:
Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35 This generated password is for development use only. Your security configuration must be updated before running your application in production.
Если вы настраиваете свою конфигурацию логирования, убедитесь, что категория org.springframework.boot.autoconfigure.security настроена на запись сообщений уровня WARN. В противном случае, пароль по умолчанию не будет выведен. |
Вы можете изменить имя пользователя и пароль, предоставив spring.security.user.name и spring.security.user.password.
Основные функции, которые вы получаете по умолчанию в веб-приложении:
-
Bean
UserDetailsService(илиReactiveUserDetailsServiceв случае приложения WebFlux) с хранилищем в памяти и одним пользователем с сгенерированным паролем (см.SecurityProperties.Userдля свойств пользователя). -
Вход с формой или безопасность HTTP Basic (в зависимости от заголовка
Acceptв запросе) для всего приложения (включая конечные точки Actuator, если Actuator находится в пути к классам). -
DefaultAuthenticationEventPublisherдля публикации событий аутентификации.
Вы можете предоставить другой AuthenticationEventPublisher, добавив bean для него.
4.1. Безопасность MVC
Конфигурация безопасности по умолчанию реализована в SecurityAutoConfiguration и UserDetailsServiceAutoConfiguration. SecurityAutoConfiguration импортирует SpringBootWebSecurityConfiguration для веб-безопасности и UserDetailsServiceAutoConfiguration настраивает аутентификацию, что также актуально для приложений, не являющихся веб-приложениями. Чтобы полностью отключить конфигурацию веб-приложения по умолчанию или объединить несколько компонентов Spring Security, таких как OAuth2 Client и Resource Server, добавьте bean типа SecurityFilterChain (при этом конфигурация UserDetailsService и безопасность Actuator не отключаются).
Чтобы также отключить конфигурацию UserDetailsService, можно добавить bean типа UserDetailsService, AuthenticationProvider, или AuthenticationManager.
Правила доступа можно переопределить, добавив bean пользовательской конфигурации SecurityFilterChain. Spring Boot предоставляет удобные методы для переопределения правил доступа для конечных точек Actuator и статических ресурсов. EndpointRequest можно использовать для создания RequestMatcher, основанного на свойстве management.endpoints.web.base-path. PathRequest можно использовать для создания RequestMatcher для ресурсов в часто используемых расположениях.
4.2. Безопасность WebFlux
Аналогично приложениям Spring MVC, вы можете защитить свои приложения WebFlux, добавив зависимость spring-boot-starter-security. Конфигурация безопасности по умолчанию реализована в ReactiveSecurityAutoConfiguration и UserDetailsServiceAutoConfiguration. ReactiveSecurityAutoConfiguration импортирует WebFluxSecurityConfiguration для веб-безопасности и UserDetailsServiceAutoConfiguration настраивает аутентификацию, что также актуально для приложений, не являющихся веб-приложениями. Чтобы полностью отключить конфигурацию веб-приложения по умолчанию, вы можете добавить bean типа WebFilterChainProxy (при этом конфигурация UserDetailsService и безопасность Actuator не отключаются).
Чтобы также отключить конфигурацию UserDetailsService, можно добавить bean типа ReactiveUserDetailsService или ReactiveAuthenticationManager.
Правила доступа и использование нескольких компонентов Spring Security, таких как OAuth 2 Client и Resource Server, можно настроить, добавив bean пользовательской конфигурации SecurityWebFilterChain. Spring Boot предоставляет удобные методы для переопределения правил доступа для конечных точек Actuator и статических ресурсов. EndpointRequest можно использовать для создания ServerWebExchangeMatcher, основанного на свойстве management.endpoints.web.base-path.
PathRequest можно использовать для создания ServerWebExchangeMatcher для ресурсов в часто используемых расположениях.
Например, вы можете настроить свою конфигурацию безопасности, добавив что-то вроде:
@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange((exchange) -> {
exchange.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
exchange.pathMatchers("/foo", "/bar").authenticated();
});
http.formLogin(withDefaults());
return http.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyWebFluxSecurityConfiguration {
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http.authorizeExchange { spec ->
spec.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
spec.pathMatchers("/foo", "/bar").authenticated()
}
http.formLogin(withDefaults())
return http.build()
}
}
4.3. OAuth2
OAuth2 — широко используемая система авторизации, поддерживаемая Spring.
4.3.1. Клиент
Если у вас spring-security-oauth2-client в вашем classpath, вы можете воспользоваться некоторыми функциями автоконфигурации для настройки клиентов OAuth2/Open ID Connect. Эта настройка использует свойства в OAuth2ClientProperties. Те же самые свойства применимы к сервлетным и реактивным приложениям.
Вы можете зарегистрировать несколько клиентов OAuth2 и поставщиков под префиксом spring.security.oauth2.client, как показано в следующем примере:
spring.security.oauth2.client.registration.my-login-client.client-id=abcd
spring.security.oauth2.client.registration.my-login-client.client-secret=password
spring.security.oauth2.client.registration.my-login-client.client-name=Client for OpenID Connect
spring.security.oauth2.client.registration.my-login-client.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-login-client.scope=openid,profile,email,phone,address
spring.security.oauth2.client.registration.my-login-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.my-login-client.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-login-client.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri={baseUrl}/authorized/user
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri={baseUrl}/authorized/email
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server.com/oauth2/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server.com/oauth2/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server.com/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server.com/oauth2/jwks
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name spring:
security:
oauth2:
client:
registration:
my-login-client:
client-id: "abcd"
client-secret: "password"
client-name: "Client for OpenID Connect"
provider: "my-oauth-provider"
scope: "openid,profile,email,phone,address"
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
client-authentication-method: "client_secret_basic"
authorization-grant-type: "authorization_code"
my-client-1:
client-id: "abcd"
client-secret: "password"
client-name: "Client for user scope"
provider: "my-oauth-provider"
scope: "user"
redirect-uri: "{baseUrl}/authorized/user"
client-authentication-method: "client_secret_basic"
authorization-grant-type: "authorization_code"
my-client-2:
client-id: "abcd"
client-secret: "password"
client-name: "Client for email scope"
provider: "my-oauth-provider"
scope: "email"
redirect-uri: "{baseUrl}/authorized/email"
client-authentication-method: "client_secret_basic"
authorization-grant-type: "authorization_code"
provider:
my-oauth-provider:
authorization-uri: "https://my-auth-server.com/oauth2/authorize"
token-uri: "https://my-auth-server.com/oauth2/token"
user-info-uri: "https://my-auth-server.com/userinfo"
user-info-authentication-method: "header"
jwk-set-uri: "https://my-auth-server.com/oauth2/jwks"
user-name-attribute: "name" Для поставщиков OpenID Connect, которые поддерживают OpenID Connect discovery, конфигурацию можно упростить. Поставщик должен быть настроен с issuer-uri, что является URI, который он утверждает как свой идентификатор издателя. Например, если предоставленный issuer-uri — "https://example.com", то запрос "OpenID Provider Configuration Request" будет отправлен на "https://example.com/.well-known/openid-configuration". В результате ожидается получение ответа "OpenID Provider Configuration Response". Следующий пример показывает, как настроить поставщика OpenID Connect с issuer-uri;
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/ spring:
security:
oauth2:
client:
provider:
oidc-provider:
issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/" По умолчанию OAuth2LoginAuthenticationFilter Spring Security обрабатывает только URL, соответствующие /login/oauth2/code/*. Если вы хотите настроить redirect-uri на использование другого шаблона, вам необходимо предоставить конфигурацию для обработки этого пользовательского шаблона. Например, для сервлетных приложений вы можете добавить свою собственную SecurityFilterChain, подобную следующей:
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MyOAuthClientConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
)
.oauth2Login((login) -> login
.redirectionEndpoint((endpoint) -> endpoint
.baseUri("/login/oauth2/callback/*")
)
);
return http.build();
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
open class MyOAuthClientConfiguration {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2Login {
redirectionEndpoint {
baseUri = "/login/oauth2/callback/*"
}
}
}
return http.build()
}
}
Spring Boot автоматически настраивает InMemoryOAuth2AuthorizedClientService, который используется Spring Security для управления регистрацией клиентов. InMemoryOAuth2AuthorizedClientService имеет ограниченные возможности, и мы рекомендуем использовать его только в средах разработки. Для производственных сред рассмотрите использование JdbcOAuth2AuthorizedClientService или создание собственной реализации OAuth2AuthorizedClientService. |
Регистрация клиента OAuth2 для распространенных поставщиков
Для распространённых поставщиков OAuth2 и OpenID, включая Google, Github, Facebook и Okta, мы предоставляем набор значений по умолчанию для поставщиков (google, github, facebook, и okta, соответственно).
Если вам не нужно настраивать этих поставщиков, вы можете установить атрибут provider для того поставщика, для которого вам нужны значения по умолчанию. Кроме того, если ключ для регистрации клиента соответствует поддержанному поставщику по умолчанию, Spring Boot также определит это.
Другими словами, две конфигурации в следующем примере используют поставщика Google:
spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password spring:
security:
oauth2:
client:
registration:
my-client:
client-id: "abcd"
client-secret: "password"
provider: "google"
google:
client-id: "abcd"
client-secret: "password" 4.3.2. Сервер ресурсов
Если у вас spring-security-oauth2-resource-server в вашем classpath, Spring Boot может настроить OAuth2 сервер ресурсов. Для конфигурации JWT необходимо указать URI набора JWK или URI издателя OIDC, как показано в следующих примерах:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: "https://example.com/oauth2/default/v1/keys" spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/ spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/" Если сервер авторизации не поддерживает URI набора JWK, вы можете настроить сервер ресурсов с открытым ключом, используемым для проверки подписи JWT. Это можно сделать, используя свойство spring.security.oauth2.resourceserver.jwt.public-key-location, где значение должно указывать на файл, содержащий открытый ключ в формате PEM-кодированного x509. |
Свойство spring.security.oauth2.resourceserver.jwt.audiences можно использовать для указания ожидаемых значений утверждения aud в JWT. Например, чтобы потребовать, чтобы JWT содержали утверждение aud со значением my-audience;
spring.security.oauth2.resourceserver.jwt.audiences[0]=my-audience spring:
security:
oauth2:
resourceserver:
jwt:
audiences:
- "my-audience" Эти же свойства применимы как для сервлетных, так и для реактивных приложений. В качестве альтернативы, вы можете определить собственный JwtDecoder бин для сервлетных приложений или ReactiveJwtDecoder для реактивных приложений.
В случаях, когда используются нетокены JWT, а нежные токены, вы можете настроить следующие свойства для проверки токенов с помощью интроспекции:
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://example.com/check-token
spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id
spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: "https://example.com/check-token"
client-id: "my-client-id"
client-secret: "my-client-secret" Снова, те же самые свойства применимы и для сервлетных, и для реактивных приложений. В качестве альтернативы, вы можете определить свой собственный OpaqueTokenIntrospector бин для сервлетных приложений или ReactiveOpaqueTokenIntrospector для реактивных.
4.3.3. Сервер авторизации
Если у вас spring-security-oauth2-authorization-server в вашем classpath, вы можете воспользоваться некоторыми функциями автоконфигурации для настройки сервера авторизации OAuth2 на основе Servlet.
Вы можете зарегистрировать несколько клиентов OAuth2 под префиксом spring.security.oauth2.authorizationserver.client, как показано в следующем примере:
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-id=abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-secret={noop}secret1
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-authentication-methods[0]=client_secret_basic
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[0]=authorization_code
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[1]=refresh_token
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[0]=https://my-client-1.com/login/oauth2/code/abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[1]=https://my-client-1.com/authorized
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[0]=openid
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[1]=profile
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[2]=email
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[3]=phone
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[4]=address
spring.security.oauth2.authorizationserver.client.my-client-1.require-authorization-consent=true
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-id=efgh
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-secret={noop}secret2
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-authentication-methods[0]=client_secret_jwt
spring.security.oauth2.authorizationserver.client.my-client-2.registration.authorization-grant-types[0]=client_credentials
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[0]=user.read
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[1]=user.write
spring.security.oauth2.authorizationserver.client.my-client-2.jwk-set-uri=https://my-client-2.com/jwks
spring.security.oauth2.authorizationserver.client.my-client-2.token-endpoint-authentication-signing-algorithm=RS256 spring:
security:
oauth2:
authorizationserver:
client:
my-client-1:
registration:
client-id: "abcd"
client-secret: "{noop}secret1"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
- "refresh_token"
redirect-uris:
- "https://my-client-1.com/login/oauth2/code/abcd"
- "https://my-client-1.com/authorized"
scopes:
- "openid"
- "profile"
- "email"
- "phone"
- "address"
require-authorization-consent: true
my-client-2:
registration:
client-id: "efgh"
client-secret: "{noop}secret2"
client-authentication-methods:
- "client_secret_jwt"
authorization-grant-types:
- "client_credentials"
scopes:
- "user.read"
- "user.write"
jwk-set-uri: "https://my-client-2.com/jwks"
token-endpoint-authentication-signing-algorithm: "RS256" Свойство client-secret должно быть в формате, который может быть сопоставлен с настроенным PasswordEncoder. По умолчанию экземпляр PasswordEncoder создаётся через PasswordEncoderFactories.createDelegatingPasswordEncoder(). |
Автоконфигурация Spring Boot для Spring Authorization Server предназначена для быстрого начала работы. Большинству приложений потребуется настройка, и они захотят определить несколько бинов для переопределения автоконфигурации.
Следующие компоненты могут быть определены как бины для переопределения автоконфигурации, специфичной для Spring Authorization Server:
-
RegisteredClientRepository -
AuthorizationServerSettings -
SecurityFilterChain -
com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext> -
JwtDecoder
Spring Boot автоматически настраивает InMemoryRegisteredClientRepository , который используется Spring Authorization Server для управления зарегистрированными клиентами. InMemoryRegisteredClientRepository имеет ограниченные возможности, и мы рекомендуем использовать его только в средах разработки. Для производственных сред рассмотрите использование JdbcRegisteredClientRepository или создание собственной реализации RegisteredClientRepository. |
Дополнительную информацию можно найти в главе Getting Started справочного руководства по Spring Authorization Server.
4.4. SAML 2.0
4.4.1. Управляющая сторона
Если у вас spring-security-saml2-service-provider в вашем classpath, вы можете воспользоваться функциями автоконфигурации для настройки SAML 2.0 управляющей стороны. Эта конфигурация использует свойства в Saml2RelyingPartyProperties.
Регистрация управляющей стороны представляет собой парную конфигурацию между поставщиком удостоверений, IDP, и поставщиком услуг, SP. Вы можете зарегистрировать несколько управляющих сторон под префиксом spring.security.saml2.relyingparty, как показано в следующем примере:
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.response-url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.binding=POST
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.verification.credentials[0].certificate-location=path-to-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.entity-id=remote-idp-entity-id1
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.sso-url=https://remoteidp1.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.verification.credentials[0].certificate-location=path-to-other-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.entity-id=remote-idp-entity-id2
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.sso-url=https://remoteidp2.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.response-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST spring:
security:
saml2:
relyingparty:
registration:
my-relying-party1:
signing:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
decryption:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
singlelogout:
url: "https://myapp/logout/saml2/slo"
response-url: "https://remoteidp2.slo.url"
binding: "POST"
assertingparty:
verification:
credentials:
- certificate-location: "path-to-verification-cert"
entity-id: "remote-idp-entity-id1"
sso-url: "https://remoteidp1.sso.url"
my-relying-party2:
signing:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
decryption:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
assertingparty:
verification:
credentials:
- certificate-location: "path-to-other-verification-cert"
entity-id: "remote-idp-entity-id2"
sso-url: "https://remoteidp2.sso.url"
singlelogout:
url: "https://remoteidp2.slo.url"
response-url: "https://myapp/logout/saml2/slo"
binding: "POST" Для SAML2 выхода, по умолчанию Saml2LogoutRequestFilter и Saml2LogoutResponseFilter Spring Security обрабатывают только URL, соответствующие /logout/saml2/slo. Если вы хотите настроить url для отправки запросов на выход, инициированных AP, или response-url для отправки ответов на выход AP, для использования другого шаблона, вам необходимо предоставить конфигурацию для обработки этого пользовательского шаблона. Например, для сервлетных приложений можно добавить собственную SecurityFilterChain, подобную следующей:
@Configuration(proxyBeanMethods = false)
public class MySamlRelyingPartyConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.saml2Login(withDefaults());
http.saml2Logout((saml2) -> saml2.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
.logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")));
return http.build();
}
}
5. Spring Session
Spring Boot provides Spring Session auto-configuration for a wide range of data stores. When building a servlet web application, the following stores can be auto-configured:
-
Redis
-
JDBC
-
Hazelcast
-
MongoDB
Additionally, Spring Boot for Apache Geode provides auto-configuration for using Apache Geode as a session store.
The servlet auto-configuration replaces the need to use @Enable*HttpSession.
If a single Spring Session module is present on the classpath, Spring Boot uses that store implementation automatically. If you have more than one implementation, Spring Boot uses the following order for choosing a specific implementation:
-
Redis
-
JDBC
-
Hazelcast
-
MongoDB
-
If none of Redis, JDBC, Hazelcast and MongoDB are available, we do not configure a
SessionRepository.
When building a reactive web application, the following stores can be auto-configured:
-
Redis
-
MongoDB
The reactive auto-configuration replaces the need to use @Enable*WebSession.
Similar to the servlet configuration, if you have more than one implementation, Spring Boot uses the following order for choosing a specific implementation:
-
Redis
-
MongoDB
-
If neither Redis nor MongoDB are available, we do not configure a
ReactiveSessionRepository.
Each store has specific additional settings. For instance, it is possible to customize the name of the table for the JDBC store, as shown in the following example:
spring.session.jdbc.table-name=SESSIONS spring:
session:
jdbc:
table-name: "SESSIONS" For setting the timeout of the session you can use the spring.session.timeout property. If that property is not set with a servlet web application, the auto-configuration falls back to the value of server.servlet.session.timeout.
You can take control over Spring Session’s configuration using @Enable*HttpSession (servlet) or @Enable*WebSession (reactive). This will cause the auto-configuration to back off. Spring Session can then be configured using the annotation’s attributes rather than the previously described configuration properties.
6. Весна для GraphQL
Если вы хотите создавать приложения GraphQL, вы можете воспользоваться автоматической конфигурацией Spring Boot для Spring для GraphQL. Проект Spring для GraphQL основан на GraphQL Java. Вам как минимум понадобится spring-boot-starter-graphql starter. Поскольку GraphQL не зависит от транспорта, вам также необходимо иметь один или несколько дополнительных starter в вашем приложении для экспонирования вашего API GraphQL через веб:
| Starter | Транспорт | Реализация |
|---|---|---|
| HTTP | Spring MVC |
| WebSocket | WebSocket для приложений Servlet |
| HTTP, WebSocket | Spring WebFlux |
| TCP, WebSocket | Spring WebFlux на Reactor Netty |
6.1. Схема GraphQL
Приложению Spring GraphQL требуется определённая схема при запуске. По умолчанию вы можете записать файлы схемы «.graphqls» или «.gqls» в src/main/resources/graphql/** и Spring Boot автоматически их обнаружит. Вы можете настроить расположение с помощью spring.graphql.schema.locations и расширения файлов с помощью spring.graphql.schema.file-extensions.
Если вы хотите, чтобы Spring Boot обнаруживал файлы схемы во всех модулях вашего приложения и зависимостях для этого расположения, вы можете установить spring.graphql.schema.locations в "classpath*:graphql/**/" (обратите внимание на префикс classpath*:). |
В следующих разделах мы рассмотрим пример схемы GraphQL, определяющей два типа и два запроса:
type Query {
greeting(name: String! = "Spring"): String!
project(slug: ID!): Project
}
""" A Project in the Spring portfolio """
type Project {
""" Unique string id used in URLs """
slug: ID!
""" Project name """
name: String!
""" URL of the git repository """
repositoryUrl: String!
""" Current support status """
status: ProjectStatus!
}
enum ProjectStatus {
""" Actively supported by the Spring team """
ACTIVE
""" Supported by the community """
COMMUNITY
""" Prototype, not officially supported yet """
INCUBATING
""" Project being retired, in maintenance mode """
ATTIC
""" End-Of-Lifed """
EOL
} По умолчанию будет разрешена интроспекция полей схемы, так как она необходима для инструментов, таких как GraphiQL. Если вы хотите не отображать информацию о схеме, вы можете отключить интроспекцию, установив spring.graphql.schema.introspection.enabled в false. |
6.2. GraphQL RuntimeWiring
GraphQL Java RuntimeWiring.Builder может использоваться для регистрации пользовательских скалярных типов, директив, решателей типов, DataFetcher, и других элементов. Вы можете объявить RuntimeWiringConfigurer бин в своей Spring конфигурации, чтобы получить доступ к RuntimeWiring.Builder. Spring Boot обнаруживает такие бин и добавляет их в GraphQlSource builder.
Однако, обычно приложения не реализуют DataFetcher напрямую, а вместо этого создают аннотированные контроллеры. Spring Boot автоматически обнаружит @Controller классы с аннотированными обработчиками методов и зарегистрирует их как DataFetcher. Вот пример реализации для запроса приветствия с @Controller классом:
@Controller
public class GreetingController {
@QueryMapping
public String greeting(@Argument String name) {
return "Hello, " + name + "!";
}
}
;
@Controller
class GreetingController {
@QueryMapping
fun greeting(@Argument name: String): String {
return "Hello, $name!"
}
}
6.3. Поддержка Querydsl и QueryByExample Repositories
Spring Data предлагает поддержку как Querydsl, так и QueryByExample репозиториев. Spring GraphQL может настроить Querydsl и QueryByExample репозитории в качестве DataFetcher.
Репозитории Spring Data, аннотированные с @GraphQlRepository и расширяющие один из:
-
QuerydslPredicateExecutor -
ReactiveQuerydslPredicateExecutor -
QueryByExampleExecutor -
ReactiveQueryByExampleExecutor
обнаруживаются Spring Boot и рассматриваются как кандидаты для DataFetcher для сопоставления запросов верхнего уровня.
6.4. Транспорты
6.4.1. HTTP и WebSocket
Конечная точка GraphQL HTTP находится по адресу HTTP POST /graphql по умолчанию. Путь можно настроить с помощью spring.graphql.path.
Конечная точка HTTP для Spring MVC и Spring WebFlux предоставляется бин RouterFunction с @Order равным 0. Если вы определите свои собственные бин RouterFunction, вы можете добавить соответствующие аннотации @Order для правильного сортирования. |
Конечная точка GraphQL WebSocket отключена по умолчанию. Чтобы ее включить:
-
Для приложения Servlet добавьте WebSocket starter
spring-boot-starter-websocket -
Для приложения WebFlux дополнительные зависимости не требуются
-
Для обоих случаев, необходимо установить свойство приложения
spring.graphql.websocket.path
Spring GraphQL предоставляет модель Обработки веб-запросов. Это очень полезно для извлечения информации из заголовка HTTP-запроса и установки ее в контексте GraphQL или извлечения информации из того же контекста и записи ее в заголовок ответа. В Spring Boot вы можете объявить бин WebInterceptor для его регистрации в веб-транспорте.
Spring MVC и Spring WebFlux поддерживают запросы CORS (Cross-Origin Resource Sharing). CORS — важная часть конфигурации веб-приложения для приложений GraphQL, к которым осуществляется доступ из браузеров с использованием различных доменов.
Spring Boot поддерживает множество свойств конфигурации в пространстве имён spring.graphql.cors.*; вот краткий пример конфигурации:
spring.graphql.cors.allowed-origins=https://example.org
spring.graphql.cors.allowed-methods=GET,POST
spring.graphql.cors.max-age=1800s spring:
graphql:
cors:
allowed-origins: "https://example.org"
allowed-methods: GET,POST
max-age: 1800s 6.4.2. RSocket
RSocket также поддерживается в качестве транспорта, поверх WebSocket или TCP. После настройки RSocket сервера, мы можем настроить наш обработчик GraphQL на определённом маршруте с помощью spring.graphql.rsocket.mapping. Например, настройка этой карты как "graphql" означает, что мы можем использовать её как маршрут при отправке запросов с RSocketGraphQlClient.
Spring Boot автоматически настраивает бин RSocketGraphQlClient.Builder<?> который вы можете ввести в свои компоненты:
@Component
public class RSocketGraphQlClientExample {
private final RSocketGraphQlClient graphQlClient;
public RSocketGraphQlClientExample(RSocketGraphQlClient.Builder<?> builder) {
this.graphQlClient = builder.tcp("example.spring.io", 8181).route("graphql").build();
}
@Component
class RSocketGraphQlClientExample(private val builder: RSocketGraphQlClient.Builder<*>) {
И затем отправляем запрос:
Mono<Book> book = this.graphQlClient.document("{ bookById(id: \"book-1\"){ id name pageCount author } }")
.retrieve("bookById")
.toEntity(Book.class);
val book = graphQlClient.document(
"""
{
bookById(id: "book-1"){
id
name
pageCount
author
}
}
"""
)
.retrieve("bookById").toEntity(Book::class.java)
6.5. Обработка исключений
Spring GraphQL позволяет приложениям зарегистрировать один или несколько компонентов Spring DataFetcherExceptionResolver , которые вызываются последовательно. Исключение должно быть преобразовано в список объектов graphql.GraphQLError, см. документацию Spring GraphQL по обработке исключений. Spring Boot автоматически обнаружит бин DataFetcherExceptionResolver и зарегистрирует их в GraphQlSource.Builder.
6.6. GraphiQL и вывод схемы
Spring GraphQL предлагает инфраструктуру, помогающую разработчикам при работе с API GraphQL.
Spring GraphQL поставляется с предустановленной страницей GraphiQL, доступной по адресу "/graphiql" по умолчанию. Эта страница отключена по умолчанию и может быть включена с помощью свойства spring.graphql.graphiql.enabled . Многие приложения, которые экспонируют такую страницу, предпочитают пользовательскую сборку. Предустановленная реализация очень полезна на этапе разработки, поэтому она автоматически экспонируется с spring-boot-devtools во время разработки.
Вы также можете выбрать экспонирование схемы GraphQL в текстовом формате по адресу /graphql/schema когда свойство spring.graphql.schema.printer.enabled включено.
7. Spring HATEOAS
Если вы разрабатываете RESTful API, использующий гипермедиа, Spring Boot предоставляет автоматическую настройку для Spring HATEOAS, которая хорошо работает со многими приложениями. Автоматическая настройка заменяет необходимость использования @EnableHypermediaSupport и регистрирует ряд бинов для упрощения создания приложений, основанных на гипермедиа, включая LinkDiscoverers (для поддержки со стороны клиента) и ObjectMapper, настроенный для корректного преобразования ответов в желаемую структуру представления. ObjectMapper настраивается путем установки различных свойств spring.jackson.* или, если существует, с помощью бина Jackson2ObjectMapperBuilder.
Вы можете взять под контроль конфигурацию Spring HATEOAS, используя @EnableHypermediaSupport. Обратите внимание, что это отключает ранее описанную настройку ObjectMapper.
spring-boot-starter-hateoas специфичен для Spring MVC и не должен комбинироваться с Spring WebFlux. Для использования Spring HATEOAS с Spring WebFlux, можно добавить прямую зависимость от org.springframework.hateoas:spring-hateoas вместе с spring-boot-starter-webflux. |
8. Что читать дальше
Теперь вы должны хорошо понимать, как разрабатывать веб-приложения с помощью Spring Boot. Следующие разделы описывают, как Spring Boot интегрируется с различными технологиями данных, системами обмена сообщениями и другими возможностями ввода-вывода. Вы можете выбрать любой из них в зависимости от потребностей вашего приложения.
Copyright © 2012-2023 VMware, Inc.
Licensed under the Apache License, Version 2.0.
https://docs.spring.io/spring-boot/docs/3.1.3/reference/html/web.html