Spring Boot 中文参考指南(二)-Web

2023-03-14 10:52:16 浏览数 (2)

Spring Boot 版本 2.7.8 原文:https://docs.spring.io/spring-boot/docs/2.7.8/reference/htmlsingle/ — Spring Boot 3.x 第一个 GA 版本已于22年底发布,2.7.8 是2.x 的最后一个 GA版本,我觉得一段时间内依然会以2.x为主,该文大部分通过软件翻译,再加上自己的经验理解进行整理和注释,目的用来作为手册查找和知识扫盲。 这篇主要是Spring Boot 参考指南中的“Web”章节 最近发现了一个网站,文档翻译的也行,后续可以看这里《Spring Boot 中文文档》,后续的几章这里也不再更新 Spring Boot 中文参考指南(一) Spring Boot 中文参考指南(二)-Web Spring Boot 中文参考指南(三)-Data Spring Boot 中文参考指南(四)-消息 Spring Boot 中文参考指南(五)-IO Spring Boot 中文参考指南(六)-容器镜像 Spring Boot 中文参考指南(七)-生产就绪功能 Spring Boot 中文参考指南(八)-部署 Spring Boot 中文参考指南(九)-Spring Boot CLI Spring Boot 中文参考指南(十)-构建工具 Spring Boot 中文参考指南(十一)-“How-to” 指南 Spring Boot 中文参考指南(十二)-附录

6. Web

Spring Boot 非常适合开发Web应用程序,可以使用Tomcat、Jetty、Undertow 或 Netty 作为HTTP服务器,基于servlet的应用程序使用spring-boot-starter-web模块,响应式的Web应用程序使用spring-boot-starter-webflux

6.1 Servlet Web 应用

如果你想要构建基于servlet的web应用,可以利用Spring Boot 给Spring MVC 或者 Jersey提供的自动配置。

6.1.1 Spring Web MVC Framework

Spring MVC 允许你创建特定的@Controller 或 @RestController Bean来处理传入的HTTP请求。控制器中的方法通过使用@RequestMapping`注解映射到HTTP。

如下示例显示了一个典型的提供JSON 数据的@RestController例子:

代码语言:javascript复制
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);
    }

}
Spring MVC 自动配置

自动配置在Spring的默认配置之上添加了以下功能:

  • 包含ContentNegotiatingViewResolver Bean 和 BeanNameViewResolverBean
  • 支持服务静态资源,包括支持WebJars(后续介绍)
  • 自动注册ConverterGenericConverterFormatter Bean
  • 支持HttpMessageConverters(后续介绍)
  • 自动注册MessageCodesResolver(后续介绍)
  • 静态index.html支持
  • 自动使用ConfigurableWebBindingInitializer bean (后续介绍)

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

如果想提供RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver的自定义实例,并保留Spring Boot MVC自定义,您可以声明WebMvcRegistrations类型的bean,并使用它来提供这些组件的自定义实例。

如果想完全控制Spring MVC,您可以添加您自己的@Configuration@EnableWebMvc注解,或者添加您自己的@Configuration-annotated DelegatingWebMvcConfiguration,如@EnableWebMvc的Javadoc中所述。

Spring MVC使用的ConversionService与用于从application.propertiesapplication.yaml文件中转换值的服务不同。这意味着PeriodDurationDataSize转换器不可用,@DurationUnit@DataSizeUnit注释将被忽略。

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

笔者注:

Spring MVC自动配置由spring-boot-autoconfigure依赖中的WebMvcAutoConfiguration类加载

ContentNegotiatingViewResolver的配置

代码语言:javascript复制
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
 ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
 resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
 // ContentNegotiatingViewResolver 使用其他视图解析器来定位视图,所以应该具有较高的优先级
 reso lver.setOrder(Ordered.HIGHEST_PRECEDENCE);
 return resolver;
}

ContentNegotiatingViewResolver 本身不解析视图,而是委托给其他的viewResolver

BeanNameViewResolver的配置

代码语言:javascript复制
@Bean
@ConditionalOnBean(View.class)
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
 BeanNameViewResolver resolver = new BeanNameViewResolver();
 resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
 return resolver;
}

BeanNameViewResolver 用于将视图名称解析为上下文中的bean

WebMvcRegistrations 是一个接口,可以注册WebMvcConfigurationSupport的关键注解,以此来覆盖Spring MVC提供的默认组件

ConversionService 类型转换的服务接口

HttpMessageConverters

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

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

代码语言:javascript复制
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都会添加到转换器列表中,可以用同样的方式覆盖默认转换器。

MessageCodesResolver

Spring MVC 有一个策略来生成错误代码,用于从绑定的错误中渲染错误消息:MessageCodesResolver。如果你设置了spring.mvc.message-codes-resolver-format属性PREFIX_ERROR_CODE或者POSTFIX_ERROR_CODE,Spring Boot 会自动创建一个。

静态内容

默认的,Spring Boot 提供静态内容的路径是类路径的/static/public/resources/META-INF/resources或者ServletContext的根目录。它使用Spring MVC的ResourceHttpRequestHandler处理, 也可以通过添加自己的WebMvcConfigurer并覆盖addResourceHandlers方法来修改。

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

默认servlet充当回退,如果Spring决定不处理它,则从ServletContext的根目录中提供内容。大多数时候,这种情况不会发生(除非您修改默认的MVC配置),因为Spring始终可以通过DispatcherServlet处理请求。

默认情况下,资源映射在/**上,但您可以使用spring.mvc.static-path-pattern属性进行调整。例如,将所有资源迁移到/resources/**可以以下操作:

代码语言:javascript复制
spring.mvc.static-path-pattern=/resources/**

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

除了前面提到的“标准”静态资源位置外,还为Webjars 内容做了兼容,如果打包,任何/webjars/**的路径资源将从jar文件中获取。

如果你的应用程序被打包为jar,请勿使用/src/main/webapp目录,因为会被忽略,虽然此目录是一个常见的标准,但它仅用于war 打包。

Spring Boot 还支持Spring MVC 提供的高级资源处理功能,比如缓存破坏或为Webjars提供与版本无关的URL。

要使用Webjars的版本无关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"/>

代码语言:javascript复制
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**

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

"fixed"策略可以在不更改文件名的情况下载URL中添加静态版本字符串,如下所示:

代码语言:javascript复制
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

通过这样的配置,JavaScript模块定位/js/lib/下的资源使用fixed策略(/v12/js/lib/mymodule.js),而其他资源依然使用内容策略(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>)。

查看WebProperties.Resources,获取更多支持的选项。

此功能已在一篇专门的博客文章和Spring Framework的参考文档中进行了详尽的描述。

欢迎页

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

自定义Favicon

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

路径匹配和内容协商

Spring MVC 可以通过请求路径并将其与应用程序中定义的映射(如,控制器上的@GetMapping注解)来将传入的HTTP请求映射到处理程序。

Spring Boot 默认是禁用后缀匹配模式的,像"GET /projects/spring-boot.json"这样的地址不会跟@GetMapping("/projects/spring-boot")匹配。该功能主要用于不会发送正确的"Accept"头的HTTP客户端。

对于始终不会发送正确的 "Accept"头的客户端,可以不使用后缀匹配,而是使用查询参数,比如GET /projects/spring-boot?format=json 将映射到@GetMapping("/projects/spring-boot")

代码语言:javascript复制
spring.mvc.contentnegotiation.favor-parameter=true

或者使用不同的参数名称:

代码语言:javascript复制
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam

大多数媒体类型都支持开箱即用,但也可以定义新的媒体类型。

代码语言:javascript复制
spring.mvc.contentnegotiation.media-types.markdown=text/markdown

后缀匹配模式已被弃用,并将在未来版本中删除,如果仍然希望使用后缀匹配模式,则需要以下配置:

代码语言:javascript复制
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-suffix-pattern=true

或者,与打开所有后缀模式相比,只支持注册的后缀模式更安全:

代码语言:javascript复制
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-registered-suffix-pattern=true

从Spring Framework 5.3开始,Spring MVC支持几种将请求路径与控制器处理程序匹配的实现策略。它以前只支持AntPathMatcher策略,但现在还提供PathPatternParser。Spring Boot现在提供了一个配置属性来选择新策略:

代码语言:javascript复制
spring.mvc.pathmatch.matching-strategy=path-pattern-parser

有关此实现的更多详细信息,参考该博客文章。

PathPatternParser是一个优化的实现,但限制了某些路径模式变体的使用,并且与后缀模式匹配(spring.mvc.pathmatch.use-suffix-patternspring.mvc.pathmatch.use-registered-suffix-pattern)或将DispatcherServlet映射为servlet前缀(spring.mvc.servlet.path)。

ConfigurableWebBindingInitializer

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

模板引擎

Spring MVC 支持多种模板技术,包括Thymeleaf、FreeMarker和JSP。

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Mustache

避免使用JSP,在跟嵌入式servelt容器使用的时候存在一些已知问题。

使用其中一个模板引擎的默认配置,模板自动从src/main/resources/templates获取。

错误处理

默认情况下,Spring Boot 提供一个/error映射,以合理的方式处理所有错误,在servlet容器中它注册为一个"global"错误页。它会在机器客户端产生一个JSON响应包括error、Http状态和异常信息。对于浏览器客户端,会产生一个"whitelabel"错误视图,以HTML格式展现相同的数据(自定义的话,添加一个Vuew来解决error)。

可以通过多个server.error属性来自定义默认错误处理行为。更多配置查看附录。

要完全替换默认的行为,可以实现ErrorController并注册为Bean或者添加ErrorAttributes类型的bean替换内容。

你也可以用@ControllerAdvice来定制JSON文本或异常类型,如下所示:

代码语言:javascript复制
import javax.servlet.RequestDispatcher;
import javax.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的表示。

在一些情况下,控制器级别处理的错误不会被度量指标记录,通过将处理的异常设置为请求属性,应用程序可以确保此类异常与请求度量一起记录。

代码语言:javascript复制
import javax.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文件,结构如下:

代码语言:javascript复制
src/
  - main/
      - java/
     |     <source code>
      - resources/
          - public/
              - error/
             |    - 404.html
              - <other public assets>

使用FreeMark模板映射所有5xx错误,结构如下:

代码语言:javascript复制
src/
  - main/
      - java/
     |     <source code>
      - resources/
          - templates/
              - error/
             |    - 5xx.ftlh
              - <other templates>

对于更复杂的映射,可以添加实现ErrorViewResolver接口的bean,如下:

代码语言:javascript复制
import java.util.Map;

import javax.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;
    }

}

还可以是用常规的 @ExceptionHandler@ControllerAdvice 特性,然后ErrorController会处理

Spring MVC 之外映射错误页

对于不使用Spring MVC的应用程序,可以使用ErrorPageRegistrar接口直接注册ErrorPages。此抽象直接与底层的嵌入式servlet容器一起使用,即使没有Spring MVC DispatcherServlet 也是有效的。

代码语言:javascript复制
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,其路径最终由Filter处理(这在一些非Spring Web框架中很常见,如Jersey和Wicket),那么Filter必须明确注册为ERROR调度器,如以下示例所示:

代码语言:javascript复制
import java.util.EnumSet;

import javax.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调度器类型。

WAR部署中的错误处理

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

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

如果您正在使用Spring Security,并希望在错误页面中访问主体,则必须配置Spring Security的过滤器,以便在错误调度中调用。为此,请将spring.security.filter.dispatcher-types属性设置为async, error, forward, request

CORS支持

跨域资源共享(CORS)是由大多数浏览器实现的W3C规范,允许您以灵活的方式指定哪种跨域请求被授权,而不是使用一些安全性较低且功能较弱的方法,如IFRAME或JSONP。

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

代码语言:javascript复制
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/**");
            }

        };
    }

}

6.1.2 JAX-RS 和 Jersey

如果您更喜欢REST端点的JAX-RS编程模型,您可以使用其中一个可用的实现,而不是Spring MVC。Jersey和Apache CXF开箱即用。CXF要求您在应用程序上下文中将其ServletFilter注册为@Bean。Jersey有一些原生的Spring支持,因此我们还在Spring Boot中为其提供自动配置支持,以及启动器。

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

代码语言:javascript复制
import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.stereotype.Component;

@Component
public class MyJerseyConfig extends ResourceConfig {

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

}

Jersey对扫描可执行档案的支持相当有限。例如,当运行可执行的war文件时,它无法扫描完全可执行的jar文件或WEB-INF/classes中找到的包中的端点。为了避免这种限制,不应使用packages方法,并且应使用register方法单独注册端点,如前例所示。

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

所有注册的端点都应该是带有HTTP资源注解的@Components(@GET等),如以下示例所示:

代码语言:javascript复制
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.springframework.stereotype.Component;

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

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

}

由于Endpoint是Spring @Component,其生命周期由Spring管理,您可以使用@Autowired注释注入依赖项,并使用@Value注释注入外部配置。默认情况下,Jersey servlet被注册并映射到/*。您可以通过将@ApplicationPath添加到ResourceConfigResourceConfig更改映射。

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

6.1.3 嵌入式Servlet容器支持

对于servlet应用程序,Spring Boot包括对嵌入式Tomcat、Jetty和Undertow服务器的支持。大多数开发人员使用适当的“Starter”来获取完全配置的实例。默认情况下,嵌入式服务器在port8080上监听HTTP请求。

Servlet、过滤器和监听器

使用嵌入式servlet容器时,您可以通过使用Springbean或扫描servlet组件,从servlet规范中注册servlet、过滤器和所有侦听器(如HttpSessionListener)。

将Servlet、过滤器和监听器注册为Spring Beans

任何作为Spring bean的ServletFilter或servlet*Listener实例都注册在嵌入式容器中。如果您想在配置期间引用application.properties中的值,这可能会特别方便。

默认情况下,如果上下文仅包含单个Servlet,则将其映射到/。在多个servlet bean的情况下,bean名称用作路径前缀。过滤器映射到/*

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

过滤bean不有序通常是安全的。如果需要指定顺序,您应该用@Order注解Filter或使其实现Ordered。您无法通过用@Order注解其bean方法来配置Filter的顺序。如果您无法将Filter类更改为添加@Order或实现Ordered,则必须为Filter定义FilterRegistrationBean,并使用setOrder(int)方法设置注册bean的顺序。避免配置在Ordered.HIGHEST_PRECEDENCE读取请求主体的过滤器,因为它可能与应用程序的字符编码配置相拢。如果servlet过滤器包装了请求,则应配置小于或等于OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER的顺序。

要查看应用程序中每个Filter的顺序,请为web日志组启用调试级别日志记录(logging.level.web=debug)。然后,将在启动时记录已注册过滤器的详细信息,包括其订单和URL模式。

注册Filterbean时要小心,因为它们在应用程序生命周期的早期就被初始化了。如果您需要注册与其他bean交互的Filter,请考虑使用DelegatingFilterProxyRegistrationBean

Servlet 上下文初始化

嵌入式servlet容器不直接执行servlet 3.0 javax.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来支持嵌入式servelt容器。ServletWebServerApplicationContext是一种特殊的WebApplicationContext,它通过搜索单个ServletWebServerFactory bean来自我引导。通常会自动配置TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory

通常不需要了解这些实现类。大多数应用程序都是自动配置的,而且将根据你的要求创建适当的ApplicationContextServletWebServerFactory

在嵌入式容器设置中,ServletContext 在应用程序上下文初始化期间的服务器启动过程中设置。因为,ApplicationContext中的bean无法使用ServletContext可靠地初始化。解决这个问题的一种方法是将ApplicationContext作为bean的依赖项注入,并仅在需要时访问ServletContext。另一种方法是在服务器启动后使用回调。这可以使用ApplicationListener完成,它监听ApplicationStartedEvent,如下所示:

代码语言:javascript复制
import javax.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();
    }

}
自定义嵌入式Servelt容器

可以使用Spring Environment属性配置常用的servlet容器设置。通常,您将在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 compression

Spring Boot尽可能地暴露常见设置,但这并不总是可能的。 对于这些情况,专用名称空间提供特定服务器的定制(请参见server.tomcatserver.undertow)。 例如,可以使用嵌入式servlet容器的特定功能配置访问日志。

有关完整列表,请参阅ServerProperties类。

SameSite Cookies

该SameSite cookie属性可由Web浏览器用于控制cookie在跨站点请求中是否提交,以及如何提交。当属性缺失时,该属性对于现代Web浏览器尤为重要,因为它们开始改变默认值。

如果你想更改会话cookie的SameSite属性,你可以使用server.servlet.session.cookie.same-site属性。这个属性被自动配置的Tomcat、Jetty和Undertow服务器所支持。

例如,如果您希望会话cookie具有NoneSameSite属性,您可以将以下内容添加到您的application.propertiesapplication.yaml文件中:

代码语言:javascript复制
server.servlet.session.cookie.same-site=none

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

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

代码语言:javascript复制
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.*");
    }

}
程序化定制

如果您需要以编程方式配置嵌入式servlet容器,您可以注册实现WebServerFactoryCustomizer接口的Spring Bean。WebServerFactoryCustomizer提供对ConfigurableServletWebServerFactory的访问,其中包括许多自定义设置方法。以下示例显示了以编程方式设置端口:

代码语言:javascript复制
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);
    }

}

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

代码语言:javascript复制
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支持有一些限制。

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

6.2 响应式Web应用

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

6.2.1 Spring WebFlux Framework

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

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

代码语言:javascript复制
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.fn”是功能变体,将路由配置与请求的实际处理分开,如以下示例所示:

代码语言:javascript复制
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();
    }

}
代码语言:javascript复制
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是Spring框架的一部分,详细信息可在其参考文档中找到。

您可以定义任意数量的RouterFunctionbean,以模块化路由器的定义。如果您需要优先应用,可以对bean定义顺序。

spring-boot-starter-webflux模块添加到应用中以开始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实例配置编解码器(在本文档后面描述)。
  • 支持提供静态资源,包括支持WebJars(在本文档后面描述)。

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

如果想要完全控制Spring WebFlux,可以添加自己的@Configuration,并用@EnableWebFlux标注。

带有HttpMessageReaders和HttpMessageWriters的HTTP编解码器

Spring WebFlux 使用HttpMessageReaderHttpMessageWriter接口来转换HTTP请求和响应。 他们使用 CodecConfigurer 配置了合理的默认值,这样就可以通过查看您的类路径中可用的库来实现。

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

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

代码语言:javascript复制
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/**可以实现以下操作:

代码语言:javascript复制
spring.webflux.static-path-pattern=/resources/**

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

除了前面列出的“标准”静态资源位置外,Webjars内容也有一个特殊情况。任何在/webjars/**具有路径的资源,如果以Webjars格式打包,则从jar文件提供。

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

欢迎页

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

模板引擎

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

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

  • FreeMarker
  • Thymeleaf
  • Mustache

当您使用这些模板引擎之一进行默认配置时,您的模板会自动从src/main/resources/templates挑选出来。

错误处理

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

自定义此功能的第一步通常涉及使用现有机制,但替换或增强错误内容。为此,您可以添加ErrorAttributes类型的bean。

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

代码语言:javascript复制
import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
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.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, 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();
    }

}

6.2.2 嵌入式响应服务支持

Spring Boot包括对以下嵌入式反应式网络服务器的支持:Reactor Netty、Tomcat、Jetty和Undertow。大多数开发人员使用适当的“Starter”来获取完整配置的实例。默认情况下,嵌入式服务器监听端口8080上的HTTP请求。

6.2.3 响应式服务资源配置

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

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

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

开发人员可以通过提供自定义ReactorResourceFactoryJettyResourceFactory bean来覆盖Jetty和ReactorNetty的资源配置,应用于客户端和服务器。

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

6.3 优雅关机

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

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

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

代码语言:javascript复制
server.shutdown=graceful

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

代码语言:javascript复制
spring.lifecycle.timeout-per-shutdown-phase=20s

如果IDE没有发送正确的SIGTERM信号,那么在IDE中使用优雅的关机可能无法正常工作。有关更多详细信息,请参阅您的IDE文档。

6.4 Spring Security

如果Spring Security在类路径上,那么Web应用程序默认情况下是安全的。Spring Boot依靠Spring Security的内容协商策略来决定是使用httpBasic还是formLogin。要向Web应用程序添加方法级安全性,您还可以使用所需的设置添加@EnableGlobalMethodSecurity。更多信息可以在Spring Security参考指南中找到。

默认的UserDetailsService只有一个用户。用户名是user,密码是随机的,在应用程序启动时以WARN级别打印,如以下示例所示:

代码语言:javascript复制
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.namespring.security.user.password修改用户名和密码。

默认情况下,您在Web应用程序中获得的基本功能是:

  • 具有内存存储的UserDetailsService(或ReactiveUserDetailsService,如果是WebFlux应用程序)bean和自动生成密码的单个用户(有关用户的属性,请参阅SecurityProperties.User)。
  • 整个应用程序(如果actuator在类路径上,则包括actuator端点)的基于表单的登录或HTTP基本安全性(取决于请求中的Accept标头)。
  • 用于发布身份验证事件的DefaultAuthenticationEventPublisher

您可以通过为其添加bean来提供不同的AuthenticationEventPublisher

MVC 安全

默认安全配置在SecurityAutoConfigurationUserDetailsServiceAutoConfiguration中实现。SecurityAutoConfiguration会导入用于web安全的SpringBootWebSecurityConfigurationUserDetailsServiceAutoConfiguration用于配置身份验证,这也适用于非web应用程序。要完全关闭默认的Web应用程序安全配置或合并多个Spring Security组件,如OAuth2客户端和资源服务器,请添加SecurityFilterChain类型的bean(这样做不会禁用UserDetailsService配置或执行器的安全性)。

要关闭UserDetailsService配置,您可以添加UserDetailsServiceAuthenticationProviderAuthenticationManager类型的bean。

可以通过添加自定义SecurityFilterChainWebSecurityConfigurerAdapter来覆盖访问规则。Spring Boot提供了方便的方法,可用于覆盖actuator端点和静态资源的访问规则。EndpointRequest可用于创建基于management.endpoints.web.base-path属性的RequestMatcherPathRequest可用于为常用位置的资源创建RequestMatcher

WebFlux 安全

与Spring MVC应用程序类似,您可以通过添加spring-boot-starter-security依赖项来保护WebFlux应用程序。默认安全配置在ReactiveSecurityAutoConfigurationUserDetailsServiceAutoConfiguration中实现。ReactiveSecurityAutoConfiguration导入WebFluxSecurityConfiguration用于Web安全,UserDetailsServiceAutoConfiguration配置身份验证,这也与非Web应用程序相关。要完全关闭默认的Web应用程序安全配置,您可以添加WebFilterChainProxy类型的bean(这样做不会禁用UserDetailsService配置或执行器的安全性)。

要关闭UserDetailsService配置,您可以添加ReactiveUserDetailsServiceReactiveAuthenticationManager类型的bean。

可以通过添加自定义SecurityFilterChainWebSecurityConfigurerAdapter bean来覆盖访问规则。Spring Boot提供了方便的方法,可用于覆盖执行器端点和静态资源的访问规则。

EndpointRequest可用于创建基于management.endpoints.web.base-path属性的RequestMatcher。PathRequest可用于为常用位置的资源创建RequestMatcher。

例如,您可以通过添加以下内容来自定义安全配置:

代码语言:javascript复制
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

OAuth2是一个广泛使用的授权框架

Client

如果您的类路径上有spring-security-oauth2-client,您可以利用一些自动配置来设置OAuth2/Open ID Connect客户端。此配置使用OAuth2ClientProperties下的属性。相同的属性适用于servlet和reactive应用程序。您可以在spring.security.oauth2.client前缀下注册多个OAuth2客户端和提供商,如以下示例所示:

代码语言:javascript复制
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=https://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=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=https://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=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/oauth/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server/oauth/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server/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/token_keys
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name

对于支持OpenID Connect discovery的OpenID Connect提供商,可以进一步简化配置。提供商需要配置issuer-uri,这是它声称作为其发行人标识符的URI。例如,如果提供的issuer-uri是“https://example.com”,则将向“https://example.com/.well-known/openid-configuration”发出`OpenID Provider Configuration Request结果预计将是OpenID Provider Configuration Response。以下示例展示了如何使用issuer-uri`配置OpenID Connect提供程序:

代码语言:javascript复制
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

代码语言:javascript复制
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;

@Configuration(proxyBeanMethods = false)
public class MyOAuthClientConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
        http.oauth2Login((login) -> login.redirectionEndpoint().baseUri("custom-callback"));
        return http.build();
    }

}

Spring Boot自动配置InMemoryOAuth2AuthorizedClientService,Spring Security用于管理客户端注册。InMemoryOAuth2AuthorizedClientService的功能有限,我们建议仅将其用于开发环境。对于生产环境,请考虑使用JdbcOAuth2AuthorizedClientService或创建您自己的OAuth2AuthorizedClientService`实现。

通用 OAuth2 Client Registration

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

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

以下示例中的两种配置都使用谷歌提供商:

代码语言:javascript复制
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
Resource Server

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

代码语言:javascript复制
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys
代码语言:javascript复制
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格式公钥的文件。

属性同样适用于servlet和反应式应用程序。

或者,您可以为servlet应用程序定义自己的JwtDecoder bean,或者为反应式应用程序定义ReactiveJwtDecode

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

代码语言:javascript复制
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

同样,属性适用于servlet和反应式应用程序。

或者,您可以为servlet应用程序定义自己的OpaqueTokenIntrospector bean,或为反应式应用程序定义ReactiveOpaquetokenIntrosector

Authorization Server

目前,Spring Security不实现OAuth 2.0授权服务器。然而,此功能可从Spring Security OAuth项目获得,该项目最终将被Spring Security完全接纳。在此之前,您可以使用spring-security-oauth2-autoconfigure模块设置OAuth 2.0授权服务器;有关说明,请参阅其文档。

笔者注: 笔者有一关于Spring Security OAuth2 相关的专栏,欢迎阅读

SAML 2.0
依赖方

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

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

代码语言:javascript复制
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.reponse-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST

对于SAML2注销,默认情况下,Spring Security的Saml2LogoutRequestFilterSaml2LogoutResponseFilter仅处理与/logout/saml2/slo匹配的URL。如果您想自定义AP发起的注销请求发送到的url或AP发送注销响应的response-url,要使用不同的模式,您需要提供配置来处理该自定义模式。例如,对于servlet应用程序,您可以添加类似于以下内容的SecurityFilterChain

代码语言:javascript复制
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;

@Configuration(proxyBeanMethods = false)
public class MySamlRelyingPartyConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
        http.saml2Login();
        http.saml2Logout((saml2) -> saml2.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
                .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")));
        return http.build();
    }

}

6.5 Spring Session

Spring Boot为各种数据存储提供Spring Session自动配置。在构建servlet Web应用程序时,可以自动配置以下存储:

  • JDBC
  • Redis
  • Hazelcast
  • MongoDB

此外,Spring Boot Apache Geode 为Apache Geode 作为会话存储提供了自动配置。

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

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

  • Redis
  • MongoDB

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

如果类路径上存在单个Spring Session模块,Spring Boot会自动使用该存储实现。如果您有多个实现,则必须选择要用于存储会话的StoreType。例如,要使用JDBC作为后端存储,您可以按以下方式配置应用程序:

代码语言:javascript复制
spring.session.store-type=jdbc

可以将store-type设置为none来禁用Spring Session

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

代码语言:javascript复制
spring.session.jdbc.table-name=SESSIONS

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

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

0 人点赞