Spring Boot Web中文文档

本文为官方文档直译版本。原文链接

Spring Boot Web中文文档

    • 引言
    • [Servlet Web Applications](#Servlet Web Applications)
      • [Spring Web MVC Framework](#Spring Web MVC Framework)
      • [JAX-RS 和 Jersey](#JAX-RS 和 Jersey)
      • [嵌入式 Servlet 容器支持](#嵌入式 Servlet 容器支持)
        • [Servlets, Filters, 和 Listeners](#Servlets, Filters, 和 Listeners)
          • [将 Servlet、Filters和Listeners注册为 Spring Bean](#将 Servlet、Filters和Listeners注册为 Spring Bean)
        • [初始化 Servlet 上下文](#初始化 Servlet 上下文)
          • [扫描Servlets, Filters, 和 listeners](#扫描Servlets, Filters, 和 listeners)
        • ServletWebServerApplicationContext
        • [自定义嵌入式 Servlet 容器](#自定义嵌入式 Servlet 容器)
          • [SameSite Cookie](#SameSite Cookie)
          • 字符编码
          • 程序自定义
          • [直接自定义 ConfigurableServletWebServerFactory](#直接自定义 ConfigurableServletWebServerFactory)
        • [JSP 的局限](#JSP 的局限)
    • [Reactive Web Applications](#Reactive Web Applications)
    • 优雅关机
    • [Spring Security](#Spring Security)
    • [Spring Session](#Spring Session)
    • [Spring for GraphQL](#Spring for GraphQL)
      • [GraphQL Schema](#GraphQL Schema)
      • [GraphQL RuntimeWiring](#GraphQL RuntimeWiring)
      • [支持 Querydsl 和 QueryByExample 资源库](#支持 Querydsl 和 QueryByExample 资源库)
      • 传输方式
        • [HTTP 和 WebSocket](#HTTP 和 WebSocket)
      • RSocket
      • 异常处理
      • [GraphiQL 和 Schema 打印](#GraphiQL 和 Schema 打印)
    • [Spring HATEOAS](#Spring HATEOAS)

引言

Spring Boot 非常适合网络应用程序开发。您可以使用嵌入式 Tomcat、Jetty、Undertow 或 Netty 创建一个独立的 HTTP 服务器。大多数网络应用程序都使用 spring-boot-starter-web 模块来快速启动和运行。你也可以选择使用 spring-boot-starter-webflux 模块来构建反应式网络应用程序。

如果尚未开发 Spring Boot Web 应用程序,可以按照入门部分中的 "Hello World!"示例进行开发。

Servlet Web Applications

如果想构建基于 servlet 的网络应用程序,可以利用 Spring Boot 为 Spring MVC 或 Jersey 提供的自动配置功能。

Spring Web MVC Framework

Spring Web MVC Framework(通常称为 "Spring MVC")是一个丰富的 "模型、视图、控制器" Web 框架。Spring MVC 可让您创建特殊的 @Controller@RestController Bean 来处理传入的 HTTP 请求。控制器中的方法通过 @RequestMapping 注解映射到 HTTP。

下面的代码展示了一个典型的 @RestController,它提供 JSON 数据:

java 复制代码
import java.util.List;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@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);
    }

}

功能变体 "WebMvc.fn" 将路由配置与请求的实际处理分离开来,如下例所示:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;

@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();
    }

}
java 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

@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();
    }

}

Spring MVC 是 Spring 框架核心的一部分,详细信息请参见参考文档spring.io/guides 网站上也有多份介绍 Spring MVC 的指南。

您可以定义任意数量的 RouterFunction Bean,以便将路由器的定义模块化。如果需要应用优先级,可以对Bean进行排序。

Spring MVC 自动配置

Spring Boot 为 Spring MVC 提供了自动配置功能,可与大多数应用程序配合使用。它取代了 @EnableWebMvc 的需要,但两者不能同时使用。除了 Spring MVC 的默认设置外,自动配置还提供以下功能:

  • 包含 ContentNegotiatingViewResolverBeanNameViewResolver Bean。
  • 支持静态资源服务,包括对 WebJar 的支持(本文档稍后将介绍)。
  • 自动注册 ConverterGenericConverterFormatter Bean。
  • 支持 HttpMessageConverters(本文档稍后将介绍)。
  • 自动注册 MessageCodesResolver(本文档稍后将介绍)。
  • 支持静态 index.html
  • 自动使用 ConfigurableWebBindingInitializer Bean(本文档稍后将介绍)。

如果你想保留这些 Spring Boot MVC 自定义,并进行更多 MVC 自定义(拦截器、格式化器、视图控制器和其他功能),你可以添加自己的 @Configuration 类,其类型为 WebMvcConfigurer,但不包含 @EnableWebMvc

如果您想提供 RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver 的自定义实例,并仍然保留 Spring Boot MVC 的自定义功能,您可以声明一个 WebMvcRegistrations 类型的 Bean,并使用它来提供这些组件的自定义实例。自定义实例将由 Spring MVC 进一步初始化和配置。要参与后续处理,或在需要时覆盖后续处理,应使用 WebMvcConfigurer

Spring MVC 转换服务

Spring MVC 使用不同的转换服务(ConversionService)来转换 application.propertiesapplication.yaml 文件中的值。这意味着 Period、Duration 和 DataSize 转换器不可用,并且 @DurationUnit@DataSizeUnit 注解将被忽略。

如果想定制 Spring MVC 使用的 ConversionService,可以提供一个带有 addFormatters 方法的 WebMvcConfigurer Bean。通过该方法,你可以注册任何你喜欢的转换器,也可以委托使用 ApplicationConversionService 上可用的静态方法。

也可使用 spring.mvc.format.* 配置属性自定义转换。未配置时,将使用以下默认值:

属性 DateTimeFormatter
spring.mvc.format.date ofLocalizedDate(FormatStyle.SHORT)
spring.mvc.format.time ofLocalizedTime(FormatStyle.SHORT)
spring.mvc.format.date-time ofLocalizedDateTime(FormatStyle.SHORT)
HttpMessageConverters

Spring MVC 使用 HttpMessageConverter 接口来转换 HTTP 请求和响应。开箱即包含合理的默认设置。例如,对象可自动转换为 JSON(通过使用 Jackson 库)或 XML(通过使用 Jackson XML 扩展(如果可用),或通过使用 JAXB(如果 Jackson XML 扩展不可用))。默认情况下,字符串以 UTF-8 编码。

如果需要添加或自定义转换器,可以使用 Spring Boot 的 HttpMessageConverters 类,如下表所示:

java 复制代码
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {

    @Bean
    public HttpMessageConverters customConverters() {
        HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
        HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
        return new HttpMessageConverters(additional, another);
    }

}

上下文中存在的任何 HttpMessageConverter Bean 都会被添加到转换器列表中。您也可以用同样的方法覆盖默认转换器。

MessageCodesResolver

Spring MVC 有一种生成错误代码的策略,用于从绑定错误中渲染错误信息: MessageCodesResolver. 如果您设置了 spring.mvc.message-codes-resolver-format 属性等于 PREFIX_ERROR_CODEPOSTFIX_ERROR_CODE,Spring Boot 就会为您创建一个(参见 DefaultMessageCodesResolver.Format 中的枚举)。

静态内容

默认情况下,Spring Boot 会从类路径中名为 /static(或 /public/resources/META-INF/resources)的目录或 ServletContext 的根目录提供静态内容。它使用 Spring MVC 中的 ResourceHttpRequestHandler,因此您可以通过添加自己的 WebMvcConfigurer 并覆盖 addResourceHandlers 方法来修改该行为。

在独立网络应用程序中,容器中的默认 servlet 未启用。可以使用 server.servlet.register-default-servlet 属性启用它。

默认 servlet 充当后备角色,如果 Spring 决定不处理请求,它将从 ServletContext 的根中提供内容。大多数情况下,这种情况不会发生(除非你修改了默认 MVC 配置),因为 Spring 总是可以通过 DispatcherServlet 处理请求。

默认情况下,资源映射到 /**,但可以使用 spring.mvc.static-path-pattern 属性进行调整。例如,将所有资源重新定位到 /resources/** 可按如下方式实现:

yaml 复制代码
spring:
  mvc:
    static-path-pattern: "/resources/**"

您还可以使用 spring.web.resources.static-locations 属性自定义静态资源位置(用目录位置列表替换默认值)。servlet 上下文根路径"/"也会自动添加为位置。

除了前面提到的 "标准" 静态资源位置外,Webjars 内容还有一个特殊情况。默认情况下,任何路径为 /webjars/** 的资源,如果是以 Webjars 格式打包的,都是通过 jar 文件提供的。可以使用 spring.mvc.webjars-path-pattern 属性自定义路径。

如果应用程序打包为 jar,请勿使用 src/main/webapp 目录。虽然该目录是一个通用标准,但它只适用于 war 包,如果生成 jar,大多数构建工具都会忽略它。

Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许使用缓存破坏静态资源或为 Webjar 使用版本无关 URL 等用例。

要为 Webjar 使用版本无关的 URL,请添加 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

要使用缓存消除,以下配置将为所有静态资源配置缓存消除解决方案,有效地在 URL 中添加内容哈希,如 <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>

yaml 复制代码
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: "/**"

由于 Thymeleaf 和 FreeMarker 自动配置了 ResourceUrlEncodingFilter,资源链接在运行时会在模板中重写。使用 JSP 时,应手动声明此过滤器。其他模板引擎目前不自动支持,但可以通过自定义macros/helpers和使用 ResourceUrlProvider 来支持。

在使用 JavaScript 模块加载器等动态加载资源时,重命名文件是不可行的。因此,我们还支持其他策略,并可将其结合使用。"fixed" 策略会在 URL 中添加一个静态版本字符串,而不会更改文件名,如下例所示:

yaml 复制代码
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: "/**"
          fixed:
            enabled: true
            paths: "/js/lib/"
            version: "v12"

使用此配置后,位于"/js/lib/"下的 JavaScript 模块将使用固定的版本管理策略("/v12/js/lib/mymodule.js"),而其他资源仍使用内容管理策略(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>)。

有关更多支持选项,请参阅 WebProperties.Resources

Spring Framework 的参考文档和一篇专门的博文对这一功能进行了详细描述。

欢迎页面

Spring Boot 支持静态和模板化欢迎页面。它首先在配置的静态内容位置查找 index.html 文件。如果找不到,它就会查找索引模板。如果找到其中之一,它将自动用作应用程序的欢迎页面。

这仅作为应用程序定义的实际索引路由的备用。排序由 HandlerMapping Bean 的顺序定义,默认情况下如下:

RouterFunctionMapping 使用 RouterFunction Bean 声明的端点
RequestMappingHandlerMapping @Controller Bean 中声明的端点
WelcomePageHandlerMapping 欢迎页面支持
自定义图标

与其他静态资源一样,Spring Boot 会检查配置的静态内容位置中是否有 favicon.ico。如果存在这样的文件,就会自动将其用作应用程序的图标。

路径匹配和内容协商

Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射(例如控制器方法上的 @GetMapping 注解)进行匹配,从而将传入的 HTTP 请求映射到处理程序。

Spring Boot 选择默认禁用后缀模式匹配,这意味着 "GET /projects/spring-boot.json" 之类的请求不会匹配到 @GetMapping("/projects/spring-boot") 映射。这被视为 Spring MVC 应用程序的最佳实践。这一功能在过去主要适用于不发送正确 "Accept" 请求头的 HTTP 客户端;我们需要确保向客户端发送正确的内容类型。如今,内容协商功能更加可靠。

我们还有其他方法来处理那些不能持续发送正确的 "Accept" 请求头的 HTTP 客户端。我们可以不使用后缀匹配,而是使用查询参数来确保类似 "GET /projects/spring-boot?format=json" 的请求会被映射到 @GetMapping("/projects/spring-boot")

yaml 复制代码
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true

或者,如果您希望使用不同的参数名称:

yaml 复制代码
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: "myparam"

开箱即支持大多数标准媒体类型,但您也可以定义新的媒体类型:

yaml 复制代码
spring:
  mvc:
    contentnegotiation:
      media-types:
        markdown: "text/markdown"

从 Spring Framework 5.3 开始,Spring MVC 支持两种将请求路径匹配到控制器的策略。默认情况下,Spring Boot 使用 PathPatternParser 策略。PathPatternParser 是一种优化的实现,但与 AntPathMatcher 策略相比有一些限制。PathPatternParser 限制使用某些路径模式变体。此外,它还与使用路径前缀(spring.mvc.servlet.path)配置 DispatcherServlet 不兼容。

可以使用 spring.mvc.pathmatch.matching-strategy 配置属性来配置策略,如下例所示:

yaml 复制代码
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,以完全禁用静态内容服务。

ConfigurableWebBindingInitializer

Spring MVC 使用 WebBindingInitializer 为特定请求初始化 WebDataBinder。如果你创建了自己的 ConfigurableWebBindingInitializer @Bean,Spring Boot 就会自动配置 Spring MVC 以使用它。

模板引擎

除了 REST 网络服务,您还可以使用 Spring MVC 来提供动态 HTML 内容。Spring MVC 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 JSP。此外,许多其他模板引擎也包含自己的 Spring MVC 集成。

Spring Boot 包含对以下模板引擎的自动配置支持:

如果可能,应避免使用 JSP。在嵌入式 servlet 容器中使用 JSP 时,有几个已知的限制。

在默认配置下使用这些模板引擎之一时,模板会自动从 src/main/resources/templates 抓取。

根据运行应用程序的方式,集成开发环境可能会对类路径进行不同的排序。在 IDE 中通过主方法运行应用程序时,与使用 Maven 或 Gradle 或通过打包的 jar 运行应用程序时的排序不同。这可能导致 Spring Boot 无法找到预期的模板。如果遇到这个问题,可以在 IDE 中重新排列类路径,将模块的类和资源放在前面。

错误处理

默认情况下,Spring Boot 会提供一个 /error 映射,以合理的方式处理所有错误,并在 servlet 容器中将其注册为 "全局" 错误页面。对于机器客户端,它会生成一个 JSON 响应,其中包含错误的详细信息、HTTP 状态和异常消息。对于浏览器客户端,会有一个 "whitelabel" 错误视图,以 HTML 格式显示相同的数据(要自定义该视图,可添加一个解析为errorView)。

如果要自定义默认错误处理行为,可以设置一些 server.error 属性。请参阅附录中的 "服务器属性"部分。

要完全替换默认行为,您可以实现 ErrorController 并注册该类型的 Bean 定义,或者添加 ErrorAttributes 类型的 Bean 来使用现有机制但替换内容。

BasicErrorController 可用作自定义 ErrorController 的基类。如果您想为一种新的内容类型添加一个处理程序(默认情况是专门处理 text/html,并为其他所有类型提供回退),这将特别有用。为此,请扩展 BasicErrorController,添加一个带有 produces 属性的 @RequestMapping 的公共方法,并创建一个新类型的 Bean。

Spring Framework 6.0 支持 RFC 7807 Problem Details。Spring MVC 可以使用 application/problem+json 媒体类型生成自定义错误信息,如:

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 文档,如下例所示:

java 复制代码
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@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;
    }

}

在前面的示例中,如果 MyException 是由与 SomeController 定义在同一个包中的控制器抛出的,则会使用 MyErrorBody POJO 的 JSON 表示而不是 ErrorAttributes 表示。

在某些情况下,度量基础设施不会记录控制器级处理的错误。应用程序可以通过将处理过的异常设置为请求属性,确保请求度量记录此类异常:

java 复制代码
import jakarta.servlet.http.HttpServletRequest;

import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Controller
public class MyController {

    @ExceptionHandler(CustomException.class)
    String handleCustomException(HttpServletRequest request, CustomException ex) {
        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>

要使用 FreeMarker 模板映射所有 5xx 错误,您的目录结构如下:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.ftlh
             +- <other templates>

对于更复杂的映射,您还可以添加实现 ErrorViewResolver 接口的 Bean,如下例所示:

java 复制代码
import java.util.Map;

import jakarta.servlet.http.HttpServletRequest;

import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;

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;
    }

}

您还可以使用 Spring MVC 的常规功能,如 @ExceptionHandler 方法@ControllerAdvice 等。然后,ErrorController 会接收任何未处理的异常。

在 Spring MVC 之外映射错误页面

对于不使用 Spring MVC 的应用程序,可以使用 ErrorPageRegistrar 接口直接注册 ErrorPages。这种抽象方法可直接与底层嵌入式 servlet 容器配合使用,即使没有 Spring MVC DispatcherServlet 也能正常工作。

java 复制代码
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

@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"));
    }

}

如果使用最终由过滤器处理的路径注册ErrorPage(这在Jersey和Wicket等一些非Spring Web框架中很常见),那么过滤器就必须显式注册为ERRORdispatcher,如下例所示:

java 复制代码
import java.util.EnumSet;

import jakarta.servlet.DispatcherType;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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;
    }

}

请注意,默认的 FilterRegistrationBean 不包含 ERROR dispatcher 类型。

WAR 部署中的错误处理

部署到 servlet 容器时,Spring Boot 会使用错误页面过滤器将错误状态的请求转发到相应的错误页面。这是必要的,因为 servlet 规范没有提供注册错误页面的 API。根据您部署 war 文件的容器和应用程序使用的技术,可能需要进行一些额外的配置。

只有当响应尚未提交时,错误页面过滤器才能将请求转发到正确的错误页面。默认情况下,WebSphere Application Server 8.0 及更高版本会在 servlet 的服务方法成功完成后提交响应。您应将 com.ibm.ws.webcontainer.invokeFlushAfterService 设置为 false,从而禁用此行为。

CORS 支持

跨源资源共享(CORS)是万维网联盟(W3C)的一项规范,由大多数浏览器实施,可让您以灵活的方式指定授权的跨域请求类型,而不是使用 IFRAME 或 JSONP 等安全性较低、功能较弱的方法。

从 4.2 版开始,Spring MVC 支持 CORS。在 Spring Boot 应用程序中使用带有 @CrossOrigin 注解的控制器方法 CORS 配置不需要任何特定配置。全局 CORS 配置可通过使用自定义 addCorsMappings(CorsRegistry) 方法注册 WebMvcConfigurer Bean 来定义,如下例所示:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {

            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**");
            }

        };
    }

}

JAX-RS 和 Jersey

如果你更喜欢 REST 端点的 JAX-RS 编程模型,你可以使用其中一种可用的实现来代替 Spring MVC。JerseyApache CXF 开箱即可使用。CXF 要求你在应用上下文中将其 ServletFilter注册为 @Bean。Jersey 有一些原生的 Spring 支持,因此我们还在 Spring Boot 中提供了对它的自动配置支持,同时还提供了一个启动器。

要开始使用 Jersey,请将 spring-boot-starter-jersey 作为依赖项,然后你需要一个 ResourceConfig 类型的 @Bean 来注册所有端点,如下例所示:

java 复制代码
import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.stereotype.Component;

@Component
public class MyJerseyConfig extends ResourceConfig {

    public MyJerseyConfig() {
        register(MyEndpoint.class);
    }

}

Jersey 对扫描可执行文件的支持非常有限。例如,当运行可执行 war 文件时,Jersey 无法扫描完全可执行 jar 文件WEB-INF/classes 中的包中的端点。为避免这一限制,不应使用 packages 方法,而应使用 register 方法单独注册端点,如上例所示。

对于更高级的定制,您还可以注册任意数量的实现 ResourceConfigCustomizer 的 Bean。

所有注册的端点都应是带有 HTTP 资源注解(@GET 及其他)的 @Component,如下例所示:

java 复制代码
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.springframework.stereotype.Component;

@Component
@Path("/hello")
public class MyEndpoint {

    @GET
    public String message() {
        return "Hello";
    }

}

由于端点是一个 Spring @Component,因此其生命周期由 Spring 管理,你可以使用 @Autowired 注解注入依赖关系,并使用 @Value 注解注入外部配置。默认情况下,Jersey servlet 会注册并映射到 /*。您可以在 ResourceConfig 中添加 @ApplicationPath 来更改映射。

默认情况下,Jersey 在名为 jerseyServletRegistration@Bean 类型 ServletRegistrationBean 中设置为 servlet。默认情况下,Servlet 会被懒惰地初始化,但你可以通过设置 Spring.jersey.servlet.load-on-startup 来自定义该行为。你可以创建一个自己的同名 Bean,从而禁用或覆盖该 Bean。通过设置 spring.jersey.type=filter,还可以使用过滤器代替 servlet(在这种情况下,要替换或覆盖的 @BeanjerseyFilterRegistration)。过滤器有一个 @Order,可以用 spring.jersey.filter.order 设置。将 Jersey 用作过滤器时,必须有一个 servlet 来处理任何未被 Jersey 拦截的请求。如果你的应用程序不包含这样的 servlet,你可能需要通过将 server.servlet.register-default-servlet 设置为 true 来启用默认 servlet。通过使用 spring.jersey.init.* 指定属性映射,可以为 servlet 和过滤器注册提供初始参数。

嵌入式 Servlet 容器支持

对于 servlet 应用程序,Spring Boot 包括对嵌入式 TomcatJettyUndertow 服务器的支持。大多数开发人员都会使用相应的 "Starter "来获取一个完全配置好的实例。默认情况下,嵌入式服务器通过 8080 端口侦听 HTTP 请求。

Servlets, Filters, 和 Listeners

使用嵌入式 servlet 容器时,可以通过 Spring Bean 或扫描 servlet 组件,从 servlet 规范中注册 Servlet、Filters和所有Listener(如 HttpSessionListener)。

将 Servlet、Filters和Listeners注册为 Spring Bean

任何作为 Spring Bean 的 ServletFilter Servlet *Listener 实例都会在嵌入式容器中注册。如果您想在配置过程中引用 application.properties 中的值,这将特别方便。

默认情况下,如果上下文只包含一个 Servlet,则映射到 /。过滤器映射到 /*

如果基于约定的映射不够灵活,可以使用 ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean 类来实现完全控制。

通常情况下,不对过滤器Bean进行排序是安全的。如果需要特定的顺序,则应使用 @Order 对过滤器进行注解,或使其实现 Ordered。您不能通过用 @Order 注释过滤器的 bean 方法来配置过滤器的顺序。如果无法更改过滤器类以添加 @Order 或实现 Ordered,则必须为过滤器定义一个FilterRegistrationBean ,并使用 setOrder(int) 方法设置注册 Bean 的顺序。请避免配置以 Ordered.HIGHEST_PRECEDENCE 读取请求正文的过滤器,因为它可能与应用程序的字符编码配置相冲突。如果使用 servlet 过滤器封装请求,则应配置小于或等于 OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER 的顺序。

要查看应用程序中每个过滤器的顺序,请启用网络日志组的调试级日志(logging.level.web=debug)。注册过滤器的详细信息(包括顺序和 URL 模式)将在启动时记录。
由于过滤器 Bean 在应用程序生命周期的早期就会被初始化,因此在注册过滤器 Bean 时要小心谨慎。如果您需要注册一个与其他 Bean 交互的过滤器,请考虑使用 DelegatingFilterProxyRegistrationBean

初始化 Servlet 上下文

嵌入式 servlet 容器不会直接执行 jakarta.servlet.ServletContainerInitializer 接口或 Spring 的 org.springframework.web.WebApplicationInitializer 接口。这是一个有意为之的设计决策,旨在降低为在 war 内运行而设计的第三方库破坏 Spring Boot 应用程序的风险。

如果您需要在 Spring Boot 应用程序中执行 Servlet 上下文初始化,您应该注册一个实现 org.springframework.boot.web.servlet.ServletContextInitializer 接口的 Bean。单个 onStartup 方法提供了对 ServletContext 的访问,如有必要,还可轻松用作现有 WebApplicationInitializer 的适配器。

扫描Servlets, Filters, 和 listeners

使用嵌入式容器时,可通过 @ServletComponentScan 启用注释为 @WebServlet@WebFilter@WebListener 的类的自动注册。

@ServletComponentScan 在独立容器中不起作用,而是使用容器的内置发现机制。

ServletWebServerApplicationContext

在引擎盖下,Spring Boot 使用不同类型的 ApplicationContext 来支持嵌入式 servlet 容器。ServletWebServerApplicationContext 是一种特殊类型的 WebApplicationContext,它通过搜索单个 ServletWebServerFactory Bean 进行自我引导。通常会自动配置 TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory

您通常不需要了解这些实现类。大多数应用程序都是自动配置的,相应的 ApplicationContextServletWebServerFactory 会代表你创建。

在嵌入式容器设置中,ServletContext 是在应用程序上下文初始化过程中作为服务器启动的一部分设置的。因此,ApplicationContext 中的 Bean 无法可靠地使用 ServletContext 进行初始化。解决这个问题的方法之一是将 ApplicationContext 作为 bean 的依赖注入,只有在需要时才访问 ServletContext。另一种方法是在服务器启动后使用回调。可以使用 ApplicationListener 监听 ApplicationStartedEvent,具体方法如下:

java 复制代码
import jakarta.servlet.ServletContext;

import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.web.context.WebApplicationContext;

public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {

    private ServletContext servletContext;

    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
    }

}
自定义嵌入式 Servlet 容器

常用的 servlet 容器设置可通过 Spring Environment 属性进行配置。通常,你会在 application.propertiesapplication.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)等。
  • SSL
  • HTTP 压缩

Spring Boot 尽可能公开通用设置,但这并不总是可能的。在这种情况下,专用命名空间会提供特定于服务器的自定义设置(参见 server.tomcatserver.undertow)。例如,可以使用嵌入式 servlet 容器的特定功能配置访问日志

完整列表请参阅ServerProperties

网络浏览器可使用 SameSite cookie 属性来控制是否以及如何在跨站请求中提交 cookie。该属性对现代网络浏览器尤为重要,因为它们已开始更改缺失该属性时使用的默认值。

如果要更改会话 cookie 的 SameSite 属性,可以使用 server.servlet.session.cookie.same-site 属性。自动配置的 Tomcat、Jetty 和 Undertow 服务器都支持该属性。它还用于配置基于 Spring Session servlet 的 SessionRepository Bean。

例如,如果希望会话 cookie 的 SameSite 属性为 "None",可以在 application.propertiesapplication.yaml 文件中添加以下内容:

yaml 复制代码
server:
  servlet:
    session:
      cookie:
        same-site: "none"

如果想更改添加到 HttpServletResponse 的其他 Cookie 上的 SameSite 属性,可以使用 CookieSameSiteSupplierCookieSameSiteSupplier 传递一个 Cookie,并可能返回 SameSite 值或null值。

您可以使用许多方便的工厂和过滤器方法来快速匹配特定的 Cookie。例如,添加以下 bean 将自动为所有名称与正则表达式 myapp.* 匹配的 cookie 应用 LaxSameSite

java 复制代码
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {

    @Bean
    public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
        return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
    }

}
字符编码

可使用 server.servlet.encoding.* 配置属性配置嵌入式 servlet 容器处理请求和响应的字符编码行为。

当请求的 Accept-Language 标头指明了请求的地域时,servlet 容器会自动将其映射为字符集。每个容器都会提供默认的本地语言与字符集映射,您应该确认它们是否满足您的应用程序的需要。如果不符合要求,请使用 server.servlet.encoding.mapping 配置属性自定义映射,如下例所示:

yaml 复制代码
server:
  servlet:
    encoding:
      mapping:
        ko: "UTF-8"

在上例中,ko(韩语)本地语言已被映射为 UTF-8。这相当于传统 war 部署的 web.xml 文件中的 <locale-encoding-mapping-list> 条目。

程序自定义

如果需要以编程方式配置嵌入式 servlet 容器,可以注册一个实现 WebServerFactoryCustomizer 接口的 Spring Bean。WebServerFactoryCustomizer 提供了对 ConfigurableServletWebServerFactory 的访问,其中包含大量自定义设置方法。下面的示例展示了如何通过编程设置端口:

java 复制代码
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

    @Override
    public void customize(ConfigurableServletWebServerFactory server) {
        server.setPort(9000);
    }

}

TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactoryConfigurableServletWebServerFactory 的专用变体,它们分别为 Tomcat、Jetty 和 Undertow 提供了额外的自定义设置方法。下面的示例展示了如何定制 TomcatServletWebServerFactory,以提供对 Tomcat 特定配置选项的访问:

java 复制代码
import java.time.Duration;

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    @Override
    public void customize(TomcatServletWebServerFactory server) {
        server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
    }

}
直接自定义 ConfigurableServletWebServerFactory

对于需要从 ServletWebServerFactory 扩展的高级用例,您可以自己公开一个此类 Bean。

我们为许多配置选项提供了设置器。如果您需要做一些更特殊的事情,我们还提供了几个受保护的方法 "钩子"。详情请查看源代码文档

自动配置的自定义程序仍会应用于自定义工厂,因此请谨慎使用该选项。

JSP 的局限

在运行使用嵌入式 servlet 容器(并打包为可执行存档)的 Spring Boot 应用程序时,JSP 支持会受到一些限制。

  • 如果使用 war 打包,Jetty 和 Tomcat 应该可以正常工作。使用 java -jar 启动时,可执行 war 将正常工作,而且还可以部署到任何标准容器中。使用可执行 jar 时不支持 JSP。
  • Undertow 不支持 JSP。
  • 创建自定义 error.jsp 页面不会覆盖错误处理的默认视图。应使用自定义错误页面。

Reactive Web Applications

Spring Boot 通过为 Spring Webflux 提供自动配置,简化了反应式网络应用程序的开发。

Spring WebFlux Framework

Spring WebFlux 是 Spring Framework 5.0 中引入的全新反应式 Web 框架。与 Spring MVC 不同,它不需要 servlet API,是完全异步和非阻塞的,并通过 Reactor 项目实现了 Reactive Streams 规范。

Spring WebFlux 有两种类型:功能型和基于注解型。如以下示例所示,基于注解的 WebFlux 非常接近 Spring MVC 模型:

java 复制代码
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@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);
    }

}

WebFlux 是 Spring 框架的一部分,详细信息请参阅其参考文档

"WebFlux.fn "是功能变体,它将路由配置与请求的实际处理分离开来,如下例所示:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@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();
    }

}
java 复制代码
import reactor.core.publisher.Mono;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

@Component
public class MyUserHandler {

    public Mono<ServerResponse> getUser(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> deleteUser(ServerRequest request) {
        ...
    }

}

"WebFlux.fn "是 Spring 框架的一部分,详细信息请参阅其参考文档

您可以定义任意数量的 RouterFunction Bean,以便将路由器的定义模块化。如果需要应用优先级,可以对Bean进行排序。

要开始使用,请在应用程序中添加 spring-boot-starter-webflux 模块。

在应用程序中同时添加 spring-boot-starter-webspring-boot-starter-webflux 模块会导致 Spring Boot 自动配置 Spring MVC,而不是 WebFlux。之所以选择这种行为,是因为许多 Spring 开发人员将 spring-boot-starter-webflux 添加到他们的 Spring MVC 应用程序中,以使用反应式 WebClient。您仍然可以通过将所选应用程序类型设置为 SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)来执行您的选择。

Spring WebFlux 自动配置

Spring Boot 为 Spring WebFlux 提供了自动配置,可与大多数应用程序完美配合。

自动配置在 Spring 的默认设置基础上增加了以下功能:

  • HttpMessageReaderHttpMessageWriter 实例配置编解码器(本文档稍后将介绍)。
  • 支持静态资源服务,包括对 WebJar 的支持(本文档稍后将介绍)。

如果想保留 Spring Boot WebFlux 功能并添加额外的 WebFlux 配置,可以添加自己的 @Configuration 类,该类属于 WebFluxConfigurer 类型,但不含 @EnableWebFlux

如果想完全控制 Spring WebFlux,可以添加自己的 @Configuration 类,并注释为 @EnableWebFlux

Spring WebFlux 转换服务

如果要自定义 Spring WebFlux 使用的 ConversionService,可以提供一个带有 addFormatters 方法的 WebFluxConfigurer Bean。

还可以使用 spring.webflux.format.* 配置属性自定义转换。未配置时,将使用以下默认值:

属性 DateTimeFormatter
spring.webflux.format.date ofLocalizedDate(FormatStyle.SHORT)
spring.webflux.format.time ofLocalizedTime(FormatStyle.SHORT)
spring.webflux.format.date-time ofLocalizedDateTime(FormatStyle.SHORT)
带有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器

Spring WebFlux 使用 HttpMessageReaderHttpMessageWriter 接口来转换 HTTP 请求和响应。它们通过 CodecConfigurer 进行配置,以通过查看类路径中可用的库获得合理的默认值。

Spring Boot 为编解码器提供了专用的配置属性 spring.codec.*。它还可通过使用 CodecCustomizer 实例进一步自定义。例如,spring.jackson.* 配置键适用于 Jackson 编解码器。

如果需要添加或自定义编解码器,可以创建一个自定义 CodecCustomizer 组件,如下例所示:

java 复制代码
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;

@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {

    @Bean
    public CodecCustomizer myCodecCustomizer() {
        return (configurer) -> {
            configurer.registerDefaults(false);
            configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
            // ...
        };
    }

}

您还可以利用 Boot 的自定义 JSON 序列化器和反序列化器

静态内容

默认情况下,Spring Boot 会从类路径中名为 /static(或 /public/resources /META-INF/resources)的目录中提供静态内容。它使用 Spring WebFlux 中的 ResourceWebHandler,因此您可以通过添加自己的 WebFluxConfigurer 并覆盖 addResourceHandlers 方法来修改该行为。

默认情况下,资源映射在 /**上,但你可以通过设置 spring.webflux.static-path-pattern 属性来调整。例如,将所有资源重新定位到 /resources/** 可按如下方法实现:

yaml 复制代码
spring:
  webflux:
    static-path-pattern: "/resources/**"

您还可以使用 spring.web.resources.static-locations 自定义静态资源位置。这样做可以用目录位置列表替换默认值。这样,默认的欢迎页面检测就会切换到自定义位置。因此,如果启动时在任何位置有 index.html,它就是应用程序的主页。

除了前面列出的 "标准" 静态资源位置外,Webjars 内容还有一个特殊情况。默认情况下,任何路径为 /webjars/** 的资源,如果是以 Webjars 格式打包的,都是从 jar 文件中提供的。可以使用 spring.webflux.webjars-path-pattern 属性自定义路径。

Spring WebFlux 应用程序并不严格依赖于 servlet API,因此不能作为 war 文件部署,也不使用 src/main/webapp 目录。

欢迎页面

Spring Boot 支持静态和模板化欢迎页面。它首先在配置的静态内容位置查找 index.html 文件。如果找不到,它就会查找索引模板。如果找到其中之一,它将自动用作应用程序的欢迎页面。

这仅作为应用程序定义的实际索引路由的备用。排序由 HandlerMapping Bean 的顺序定义,默认情况下如下:

RouterFunctionMapping 使用 RouterFunction Bean 声明的端点
RequestMappingHandlerMapping @Controller Bean 中声明的端点
欢迎页面的RouterFunctionMapping 欢迎页面支持
模板引擎

除了 REST 网络服务,您还可以使用 Spring WebFlux 来提供动态 HTML 内容。Spring WebFlux 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 Mustache。

Spring Boot 包含对以下模板引擎的自动配置支持:

在默认配置下使用这些模板引擎之一时,模板会自动从 src/main/resources/templates 抓取。

错误处理

Spring Boot 提供了一个 WebExceptionHandler,它能以合理的方式处理所有错误。它在处理顺序中的位置紧接在 WebFlux 提供的处理程序之前,后者被认为是最后一个。对于机器客户端,它会生成一个 JSON 响应,其中包含错误的详细信息、HTTP 状态和异常消息。对于浏览器客户端,会有一个 "whitelabel" 错误处理程序,以 HTML 格式呈现相同的数据。您还可以提供自己的 HTML 模板来显示错误(见下一节)。

在直接定制 Spring Boot 中的错误处理之前,您可以利用 Spring WebFlux 中的 RFC 7807 问题详细信息支持。Spring WebFlux 可以使用 application/problem+json 媒体类型生成自定义错误消息,例如:

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 类型的 Bean。

要更改错误处理行为,可以实现 ErrorWebExceptionHandler 并注册该类型的 Bean 定义。由于 ErrorWebExceptionHandler 相当低级,因此 Spring Boot 还提供了一个方便的 AbstractErrorWebExceptionHandler,让您以 WebFlux 功能方式处理错误,如下例所示:

java 复制代码
import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder;

@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties,
            ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) {
        super(errorAttributes, webProperties.getResources(), applicationContext);
        setMessageReaders(serverCodecConfigurer.getReaders());
        setMessageWriters(serverCodecConfigurer.getWriters());
    }

    @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();
    }

}

要想了解更全面的情况,还可以直接子类化 DefaultErrorWebExceptionHandler 并覆盖特定方法。

在某些情况下,度量基础设施不会记录在控制器或处理程序函数级别处理的错误。应用程序可以通过将处理过的异常设置为请求属性,确保请求指标记录此类异常:

java 复制代码
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.reactive.result.view.Rendering;
import org.springframework.web.server.ServerWebExchange;

@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();
    }

}
自定义错误页面

如果要为给定的状态代码显示自定义 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>

要使用 Mustache 模板映射所有 5xx 错误,您的目录结构如下:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.mustache
             +- <other templates>
Web Filters

Spring WebFlux 提供了一个 WebFilter 接口,可用于过滤 HTTP 请求-响应交换。应用程序上下文中的 WebFilter Bean 将自动用于过滤每个交换。

如果过滤器的顺序很重要,它们可以实现 Ordered 或使用 @Order 进行注解。Spring Boot 自动配置可能会为您配置网络过滤器。配置时,将使用下表所示的顺序:

Web Filter Order
WebFilterChainProxy(Spring Security) -100
HttpExchangesWebFilter Ordered.LOWEST_PRECEDENCE - 10

嵌入式反应式服务器支持

Spring Boot 支持以下嵌入式反应式 Web 服务器: Reactor Netty、Tomcat、Jetty 和 Undertow。大多数开发人员都会使用相应的 "Starter" 来获取一个完全配置好的实例。默认情况下,嵌入式服务器通过 8080 端口侦听 HTTP 请求。

自定义反应式服务器

常见的反应式网络服务器设置可通过 Spring Environment 属性进行配置。通常,你会在 application.propertiesapplication.yaml 文件中定义这些属性。

常见的服务器设置包括

  • 网络设置: 接收 HTTP 请求的监听端口(server.port)、绑定的接口地址(server.address)等。
  • 错误管理: 错误页面的位置(server.error.path)等。
  • SSL
  • HTTP compression

Spring Boot 尽可能公开通用设置,但这并不总是可能的。在这种情况下,专用命名空间(如 server.netty.*)可提供特定于服务器的自定义设置。

完整列表请参阅ServerProperties类。

程序自定义

如果需要以编程方式配置反应式 Web 服务器,可以注册一个实现 WebServerFactoryCustomizer 接口的 Spring Bean。WebServerFactoryCustomizer 提供了对 ConfigurableReactiveWebServerFactory 的访问,其中包含大量自定义设置方法。下面的示例展示了如何通过编程设置端口:

java 复制代码
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {

    @Override
    public void customize(ConfigurableReactiveWebServerFactory server) {
        server.setPort(9000);
    }

}

JettyReactiveWebServerFactoryNettyReactiveWebServerFactoryTomcatReactiveWebServerFactoryUndertowReactiveWebServerFactoryConfigurableReactiveWebServerFactory 的专用变体,它们分别为 Jetty、Reactor Netty、Tomcat 和 Undertow 提供了额外的自定义设置方法。下面的示例展示了如何自定义 NettyReactiveWebServerFactory,使其能访问特定于 Reactor Netty 的配置选项:

java 复制代码
import java.time.Duration;

import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyNettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {

    @Override
    public void customize(NettyReactiveWebServerFactory factory) {
        factory.addServerCustomizers((server) -> server.idleTimeout(Duration.ofSeconds(20)));
    }

}
直接自定义 ConfigurableReactiveWebServerFactory

对于需要从 ReactiveWebServerFactory 扩展的高级用例,您可以自己公开一个此类 Bean。

我们为许多配置选项提供了设置器。如果您需要做一些更特殊的事情,我们还提供了几个受保护的方法 "钩子"。详情请参见源代码文档

自动配置的自定义程序仍会应用于自定义工厂,因此请谨慎使用该选项。

反应式服务器资源配置

在自动配置 Reactor Netty 或 Jetty 服务器时,Spring Boot 将创建特定的 Bean,为服务器实例提供 HTTP 资源: ReactorResourceFactoryJettyResourceFactory

默认情况下,这些资源也将与 Reactor Netty 和 Jetty 客户端共享,以获得最佳性能:

  • 服务器和客户端使用相同的技术
  • 客户端实例使用 Spring Boot 自动配置的 WebClient.Builder Bean 构建

开发人员可以通过提供自定义的 ReactorResourceFactoryJettyResourceFactory Bean 来覆盖 Jetty 和 Reactor Netty 的资源配置,这将同时应用于客户端和服务器。

您可以在 WebClient Runtime 部分 了解有关客户端资源配置的更多信息。

优雅关机

所有四种嵌入式网络服务器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及基于反应和 servlet 的网络应用程序都支持优雅关机。它是关闭应用程序上下文的一部分,在停止 SmartLifecycle Bean 的最早阶段执行。这种停止处理使用一个超时时间,提供一个宽限期,在此期间允许完成现有请求,但不允许有新请求。不允许新请求的具体方式因所使用的网络服务器而异。Jetty、Reactor Netty 和 Tomcat 将在网络层停止接受请求。Undertow 会接受请求,但会立即作出服务不可用 (503) 的回应。

使用 Tomcat 优雅关机需要 Tomcat 9.0.33 或更高版本。

要启用优雅关机,请配置 server.shutdown 属性,如下例所示:

yaml 复制代码
server:
  shutdown: "graceful"

要配置超时时间,请配置 spring.lifecycle.timeout-per-shutdown-phase 属性,如下例所示:

yaml 复制代码
spring:
  lifecycle:
    timeout-per-shutdown-phase: "20s"

如果集成开发环境没有发送正确的 SIGTERM 信号,那么在集成开发环境中使用优雅关机可能无法正常工作。详情请查看集成开发环境的文档。

Spring Security

如果 Spring Security 位于类路径上,那么网络应用程序默认是安全的。Spring Boot 依靠 Spring Security 的内容协商策略来决定是使用 httpBasic 还是 formLogin。要为网络应用程序添加方法级安全性,也可以添加 @EnableGlobalMethodSecurity 并进行所需设置。更多信息可参阅《Spring 安全性参考指南》

默认的 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 来更改用户名和密码。

网络应用程序默认的基本功能包括:

  • 带有内存存储的 UserDetailsService(或 WebFlux 应用程序中的 ReactiveUserDetailsService)Bean,以及带有生成密码的单个用户(用户属性请参阅 SecurityProperties.User)。
  • 整个应用程序的基于表单的登录或 HTTP Basic 安全性(取决于请求中的 Accept 标头)(包括执行器端点,如果执行器在类路径上)。
  • 用于发布身份验证事件的 DefaultAuthenticationEventPublisher

您可以通过添加一个 Bean 来提供不同的 AuthenticationEventPublisher

MVC Security

默认安全配置在 SecurityAutoConfigurationUserDetailsServiceAutoConfiguration 中实现。SecurityAutoConfiguration 为网络安全导入 SpringBootWebSecurityConfigurationUserDetailsServiceAutoConfiguration 配置身份验证,这在非网络应用中也很重要。

要完全关闭默认的网络应用程序安全配置,或将多个 Spring 安全组件(如 OAuth2 客户端和资源服务器)组合在一起,可添加一个 SecurityFilterChain 类型的 Bean(这样做不会禁用 UserDetailsService 配置或 Actuator 的安全性)。要关闭 UserDetailsService 配置,还可以添加一个 UserDetailsServiceAuthenticationProviderAuthenticationManager 类型的 Bean。
UserDetailsService 的自动配置还将关闭类路径上的以下任何 Spring Security 模块:

  • spring-security-oauth2-client
  • spring-security-oauth2-resource-server
  • spring-security-saml2-service-provider

要在一个或多个这些依赖项之外使用 UserDetailsService,请定义您自己的 InMemoryUserDetailsManager Bean。

可以通过添加自定义 SecurityFilterChain Bean 来重写访问规则。Spring Boot 提供的便捷方法可用于重写执行器端点和静态资源的访问规则。EndpointRequest 可用于创建基于 management.endpoints.web.base-path 属性的 RequestMatcherPathRequest 可用于为常用位置的资源创建 RequestMatcher

WebFlux Security

与 Spring MVC 应用程序类似,您也可以通过添加 spring-boot-starter-security 依赖关系来确保 WebFlux 应用程序的安全。默认安全配置在 ReactiveSecurityAutoConfigurationUserDetailsServiceAutoConfiguration 中实现。ReactiveSecurityAutoConfiguration 会导入 WebFluxSecurityConfiguration 以实现网络安全,而 UserDetailsServiceAutoConfiguration 则会配置身份验证,这在非网络应用程序中也很重要。

要完全关闭默认的网络应用程序安全配置,可以添加一个 WebFilterChainProxy 类型的 Bean(这样做不会禁用 UserDetailsService 配置或 Actuator 的安全性)。要同时关闭 UserDetailsService 配置,可以添加一个 ReactiveUserDetailsServiceReactiveAuthenticationManager 类型的 Bean。

当类路径上有以下任何一个 Spring Security 模块时,自动配置也将关闭:

  • spring-security-oauth2-client
  • spring-security-oauth2-resource-server

要在一个或多个这些依赖项之外使用 ReactiveUserDetailsService,请定义您自己的 MapReactiveUserDetailsService Bean。

访问规则和多个 Spring Security 组件(如 OAuth 2 客户端和资源服务器)的使用可通过添加自定义 SecurityWebFilterChain Bean 进行配置。Spring Boot 提供的便捷方法可用于覆盖执行器端点和静态资源的访问规则。EndpointRequest 可用于创建基于 management.endpoints.web.base-path 属性的 ServerWebExchangeMatcher
PathRequest 可用于为常用位置的资源创建 ServerWebExchangeMatcher

例如,你可以通过添加以下内容来定制你的安全配置:

java 复制代码
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@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();
    }

}

OAuth2

https://oauth.net/2/OAuth2 是一个广泛使用的授权框架,由 Spring 提供支持。

Client

如果你的类路径上有 spring-security-oauth2-client,你就可以利用一些自动配置来设置 OAuth2/Open ID Connect 客户端。该配置使用 OAuth2ClientProperties 下的属性。同样的属性适用于 servlet 和反应式应用程序。

您可以在 spring.security.oauth2.client 前缀下注册多个 OAuth2 客户端和提供程序,如下例所示:

yaml 复制代码
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 提供程序,配置可以进一步简化。提供程序需要配置一个 issuer-uri,也就是作为其发行者标识符的 URI。例如,如果提供的issuer-uri 是 "https://example.com",那么将向 "https://example.com/.well-known/openid-configuration" 发出 "OpenID 提供者配置请求"。结果将是一个 "OpenID 提供商配置响应"。下面的示例显示了如何使用issuer-uri 配置 OpenID Connect 提供程序:

yaml 复制代码
spring:
  security:
    oauth2:
      client:
        provider:
          oidc-provider:
            issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"

默认情况下,Spring Security 的 OAuth2LoginAuthenticationFilter 只处理与 /login/oauth2/code/* 匹配的 URL。如果要自定义redirect-uri 以使用不同的模式,则需要提供配置来处理该自定义模式。例如,对于 servlet 应用程序,您可以添加自己的 SecurityFilterChain,类似于以下内容:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.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();
    }

}

Spring Boot 会自动配置一个 InMemoryOAuth2AuthorizedClientService,由 Spring Security 用于管理客户端注册。InMemoryOAuth2AuthorizedClientService 功能有限,建议仅在开发环境中使用。对于生产环境,请考虑使用 JdbcOAuth2AuthorizedClientService 或创建自己的 OAuth2AuthorizedClientService 实现。

通用提供商的 OAuth2 客户端注册

对于常见的 OAuth2 和 OpenID 提供商,包括 Google、Github、Facebook 和 Okta,我们提供了一组提供商默认值(分别为 Google、Github、Facebook 和 Okta)。

如果不需要自定义这些提供程序,可以将提供程序属性设置为需要推断默认值的提供程序。此外,如果客户端注册的密钥与默认支持的提供程序相匹配,Spring Boot 也会推断出该提供程序。

换句话说,下面示例中的两个配置都使用了 Google 提供程序:

yaml 复制代码
spring:
  security:
    oauth2:
      client:
        registration:
          my-client:
            client-id: "abcd"
            client-secret: "password"
            provider: "google"
          google:
            client-id: "abcd"
            client-secret: "password"
资源服务器

如果您的类路径上有 spring-security-oauth2-resource-server,Spring Boot 就能设置 OAuth2 资源服务器。对于 JWT 配置,需要指定 JWK Set URI 或 OIDC Issuer URI,如以下示例所示:

yaml 复制代码
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: "https://example.com/oauth2/default/v1/keys"
yaml 复制代码
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"

如果授权服务器不支持 JWK Set URI,则可使用用于验证 JWT 签名的公钥配置资源服务器。这可以使用 spring.security.oauth2.resourceserver.jwt.public-key-location 属性来完成,该属性的值需要指向一个包含 PEM 编码 x509 格式公钥的文件。

spring.security.oauth2.resourceserver.jwt.audiences 属性可用于指定 JWT 中 aud claim 的预期值。例如,要求 JWT 包含值为 my-audience 的 aud 声明:

yaml 复制代码
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          audiences:
            - "my-audience"

同样的属性适用于 servlet 和反应式应用程序。另外,您也可以为 servlet 应用程序定义自己的 JwtDecoder Bean,或为反应式应用程序定义 ReactiveJwtDecoder

在使用不透明令牌而不是 JWT 的情况下,您可以配置以下属性,通过自省来验证令牌:

yaml 复制代码
spring:
  security:
    oauth2:
      resourceserver:
        opaquetoken:
          introspection-uri: "https://example.com/check-token"
          client-id: "my-client-id"
          client-secret: "my-client-secret"

同样,相同的属性适用于 servlet 和反应式应用程序。另外,你也可以为 servlet 应用程序定义自己的 OpaqueTokenIntrospector Bean,或为反应式应用程序定义 ReactiveOpaqueTokenIntrospector

认证服务器

如果你的类路径上有 spring-security-oauth2-authorization-server,你就可以利用一些自动配置来设置基于 Servlet 的 OAuth2 授权服务器。

你可以在 spring.security.oauth2.authorizationserver.client 前缀下注册多个 OAuth2 客户端,如下例所示:

yaml 复制代码
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 的默认实例通过 PasswordEncoderFactories.createDelegatingPasswordEncoder() 创建。

Spring Boot 为 Spring Authorization Server 提供的自动配置是为快速上手而设计的。大多数应用程序都需要自定义,并需要定义多个 Bean 来覆盖自动配置。

以下组件可定义为 Bean,以覆盖 Spring Authorization Server 特有的自动配置:

  • RegisteredClientRepository
  • AuthorizationServerSettings
  • SecurityFilterChain
  • com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext>
  • JwtDecoder

Spring Boot 会自动配置一个 InMemoryRegisteredClientRepository,Spring 授权服务器使用它来管理注册客户端。InMemoryRegisteredClientRepository 的功能有限,建议仅在开发环境中使用。对于生产环境,请考虑使用 JdbcRegisteredClientRepository 或创建自己的 RegisteredClientRepository 实现。

更多信息可参阅《Spring 授权服务器参考指南》"入门" 章节。

SAML 2.0

依赖方

如果你的类路径上有 spring-security-saml2-service-provider,你就可以利用一些自动配置来设置 SAML 2.0 依赖方。该配置使用 Saml2RelyingPartyProperties 下的属性。

可信赖方注册代表身份提供方(IDP)和服务提供商(SP)之间的配对配置。您可以在 spring.security.saml2.relyingparty 前缀下注册多个可信赖方,如下例所示:

yaml 复制代码
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 注销,Spring Security 的 Saml2LogoutRequestFilterSaml2LogoutResponseFilter 默认只处理与 /logout/saml2/slo 匹配的 URL。如果要自定义 AP 发起注销请求时的 URL 或 AP 发送注销响应时的响应 URL,以使用不同的模式,则需要提供配置来处理该自定义模式。例如,对于 servlet 应用程序,您可以添加类似下面的 SecurityFilterChain

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@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();
    }

}

Spring Session

Spring Boot 为各种数据存储提供了 Spring Session 自动配置功能。在构建 servlet 网络应用时,可以自动配置以下存储:

  • Redis
  • JDBC
  • Hazelcast
  • MongoDB

此外,Spring Boot for Apache Geode 还提供了将 Apache Geode 用作会话存储的自动配置功能

servlet 自动配置取代了使用 @Enable*HttpSession 的需要。

如果类路径上只有一个 Spring Session 模块,Spring Boot 会自动使用该存储实现。如果有多个实现,Spring Boot 会按照以下顺序选择特定实现:

  1. Redis
  2. JDBC
  3. Hazelcast
  4. MongoDB
  5. 如果 Redis、JDBC、Hazelcast 和 MongoDB 都不可用,我们就不会配置SessionRepository

在构建反应式网络应用程序时,可以自动配置以下存储:

  • Redis
  • MongoDB

反应式自动配置取代了使用 @Enable*WebSession 的需要。

与 servlet 配置类似,如果有多个实现,Spring Boot 会按以下顺序选择特定实现:

  1. Redis
  2. MongoDB
  3. 如果 Redis 和 MongoDB 都不可用,我们就不配置 ReactiveSessionRepository

每个存储都有特定的附加设置。例如,可以自定义 JDBC 存储的表名,如下例所示:

yaml 复制代码
spring:
  session:
    jdbc:
      table-name: "SESSIONS"

要设置会话超时,可以使用 spring.session.timeout 属性。如果未在 servlet 网络应用程序中设置该属性,自动配置将退回到 server.servlet.session.timeout 的值。

你可以使用 @Enable*HttpSession (servlet) 或 @Enable*WebSession (reactive) 来控制 Spring Session 的配置。这将导致自动配置关闭。然后,就可以使用注解的属性而不是之前描述的配置属性来配置 Spring Session 了。

Spring for GraphQL

如果您想构建 GraphQL 应用程序,可以利用 Spring Boot 为 Spring for GraphQL 提供的自动配置。Spring for GraphQL 项目基于 GraphQL Java。你至少需要 spring-boot-starter-graphql starter。由于 GraphQL 与传输无关,因此您还需要在应用程序中添加一个或多个启动器,以便通过网络公开您的 GraphQL API:

Starter Transport Implementation
spring-boot-starter-web HTTP Spring MVC
spring-boot-starter-websocket WebSocket 用于 Servlet 应用程序的 WebSocket
spring-boot-starter-webflux HTTP, WebSocket Spring WebFlux
spring-boot-starter-rsocket TCP, WebSocket 基于 Reactor Netty 的 Spring WebFlux

GraphQL Schema

Spring GraphQL 应用程序需要在启动时定义模式。默认情况下,您可以在 src/main/resources/graphql/** 下编写".graphqls" 或 ".gqls" 模式文件,Spring Boot 会自动获取它们。您可以使用 spring.graphql.schema.locations 自定义位置,使用 spring.graphql.schema.file-extensions 自定义文件扩展名。

如果希望 Spring Boot 检测所有应用模块中的模式文件以及该位置的依赖关系,可以将 spring.graphql.schema.locations 设置为 "classpath*:graphql/**/"(注意前缀为 classpath*:)。

在下面的章节中,我们将考虑这个示例 GraphQL Schema,定义两种类型和两个查询:

json 复制代码
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,从而禁用内省。

GraphQL RuntimeWiring

GraphQL Java RuntimeWiring.Builder 可用于注册自定义标量类型、指令、类型解析器、DataFetcher 等。您可以在 Spring config 中声明 RuntimeWiringConfigurer Bean,以访问 RuntimeWiring.Builder。Spring Boot 会检测此类 Bean,并将它们添加到 GraphQlSource 生成器中。

不过,通常情况下,应用程序不会直接实现 DataFetcher,而是会创建注解控制器。Spring Boot 会自动检测带有注解处理程序方法的 @Controller 类,并将其注册为 DataFetchers。下面是使用 @Controller 类实现问候语查询的示例:

java 复制代码
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

@Controller
public class GreetingController {

    @QueryMapping
    public String greeting(@Argument String name) {
        return "Hello, " + name + "!";
    }

}

支持 Querydsl 和 QueryByExample 资源库

Spring Data 支持 Querydsl 和 QueryByExample 资源库。Spring GraphQL 可将 Querydsl 和 QueryByExample 资源库配置为 DataFetcher

Spring Data 资源库使用 @GraphQlRepository 进行注解,并扩展了以下功能之一:

  • QuerydslPredicateExecutor
  • ReactiveQuerydslPredicateExecutor
  • QueryByExampleExecutor
  • ReactiveQueryByExampleExecutor

被 Spring Boot 检测到,并被视为 DataFetcher 匹配顶级查询的候选项。

传输方式

HTTP 和 WebSocket

GraphQL HTTP 端点默认为 HTTP POST /graphql。路径可使用 spring.graphql.path 进行自定义。

Spring MVC 和 Spring WebFlux 的 HTTP 端点都是由 @Order0RouterFunction Bean 提供的。 如果您定义了自己的 RouterFunction Bean,可能需要添加适当的 @Order 注解,以确保它们能正确排序。

GraphQL WebSocket 端点默认关闭。要启用它:

  • 对于 Servlet 应用程序,添加 WebSocket 启动程序 spring-boot-starter-websocket
  • 对于 WebFlux 应用程序,不需要额外的依赖关系
  • 两者都必须设置 spring.graphql.websocket.path 应用程序属性

Spring GraphQL 提供了网络拦截模型。这对于从 HTTP 请求头中获取信息并将其设置在 GraphQL 上下文中,或从同一上下文中获取信息并将其写入响应头非常有用。使用 Spring Boot,您可以声明一个 WebInterceptor Bean,让它在网络传输中注册。
Spring MVCSpring WebFlux 支持 CORS(跨源资源共享)请求。对于从使用不同域的浏览器访问的 GraphQL 应用程序来说,CORS 是网络配置的重要组成部分。

Spring Boot 支持 spring.graphql.cors.* 命名空间下的许多配置属性;下面是一个简短的配置示例:

yaml 复制代码
spring:
  graphql:
    cors:
      allowed-origins: "https://example.org"
      allowed-methods: GET,POST
      max-age: 1800s

RSocket

RSocket 还支持作为 WebSocket 或 TCP 的传输方式。配置好 RSocket 服务器后,我们就可以使用 spring.graphql.rsocket.mapping 在特定路由上配置 GraphQL 处理程序。例如,将映射配置为 "graphql" 意味着我们可以在使用 RSocketGraphQlClient 发送请求时将其用作路由。

Spring Boot 会自动配置一个 RSocketGraphQlClient.Builder<?> Bean,您可以将其注入到您的组件中:

java 复制代码
@Component
public class RSocketGraphQlClientExample {

    private final RSocketGraphQlClient graphQlClient;

    public RSocketGraphQlClientExample(RSocketGraphQlClient.Builder<?> builder) {
        this.graphQlClient = builder.tcp("example.spring.io", 8181).route("graphql").build();
    }

然后发送请求:

java 复制代码
Mono<Book> book = this.graphQlClient.document("{ bookById(id: \"book-1\"){ id name pageCount author } }")
    .retrieve("bookById")
    .toEntity(Book.class);

异常处理

Spring GraphQL 使应用程序能够注册一个或多个顺序调用的 Spring DataFetcherExceptionResolver 组件。异常必须解析为 graphql.GraphQLError 对象列表,请参见 Spring GraphQL 异常处理文档。Spring Boot 会自动检测 DataFetcherExceptionResolver Bean,并将其注册到 GraphQlSource.Builder 中。

GraphiQL 和 Schema 打印

Spring GraphQL 为开发人员消费或开发 GraphQL API 提供了基础架构。

Spring GraphQL 随附一个默认 GraphiQL 页面,该页面默认显示在 "/graphiql" 中。该页面默认为禁用,可通过 spring.graphql.graphiql.enabled 属性打开。许多使用此类页面的应用程序更倾向于自定义构建。默认实现在开发过程中非常有用,这就是为什么在开发过程中它会通过 spring-boot-devtools 自动公开。

启用 spring.graphql.schema.printer.enabled 属性后,还可选择在 /graphql/schema 以文本格式公开 GraphQL Schema。

Spring HATEOAS

如果您开发的 RESTful API 需要使用超媒体,Spring Boot 会为 Spring HATEOAS 提供自动配置,以便与大多数应用程序配合使用。自动配置取代了使用 @EnableHypermediaSupport 的需要,并注册了大量 Bean 以方便构建基于超媒体的应用程序,其中包括 LinkDiscoverers(用于客户端支持)和 ObjectMapper,配置这些 Bean 是为了将响应正确编译为所需的表示形式。ObjectMapper 可通过设置各种 spring.jackson.* 属性或 Jackson2ObjectMapperBuilder Bean(如果存在)进行自定义。

您可以使用 @EnableHypermediaSupport 来控制 Spring HATEOAS 的配置。请注意,这样做会禁用前面描述的 ObjectMapper 自定义。

spring-boot-starter-hateoas 专用于 Spring MVC,不应与 Spring WebFlux 结合使用。要在 Spring WebFlux 中使用 Spring HATEOAS,可以在 Spring-boot-starter-webflux 中添加对 org.springframework.hateoas:spring-hateoas 的直接依赖。

默认情况下,接受 application/json 的请求将收到 application/hal+json 响应。要禁用此行为,请将 spring.hateoas.use-hal-as-default-json-media-type 设置为 false,并定义 HypermediaMappingInformationHalConfiguration 来配置 Spring HATEOAS,以满足应用程序及其客户端的需求。

相关推荐
Daniel 大东25 分钟前
BugJson因为json格式问题OOM怎么办
java·安全
Mr_Xuhhh30 分钟前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
永乐春秋1 小时前
WEB攻防-通用漏洞&文件上传&js验证&mime&user.ini&语言特性
前端
鸽鸽程序猿1 小时前
【前端】CSS
前端·css
ggdpzhk1 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
学不会•4 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
Theodore_10224 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸6 小时前
01-spring security认证笔记
java·笔记·spring
活宝小娜6 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
世间万物皆对象6 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试