Spring Boot非常适合Web应用程序开发。您可以通过使用内嵌的Tomcat、Jetty、Undertow或Netty来创建一个独立的HTTP服务器。大多数Web应用程序使用spring-boot-starter-web
模块快速启动。您也可以选择使用spring-boot-starter-webflux
模块构建响应式Web应用程序。
如果您还没有开发过Spring Boot Web应用程序,可以在“入门”部分中的“Hello World!”示例中进行跟随。
1. Servlet Web 应用
如果你想构建基于servlet的web应用程序,你可以利用Spring Boot为Spring MVC或Jersey提供的自动配置。
1.1. “Spring Web MVC Framework”
Spring Web MVC框架(通常称为“Spring MVC”)是一个丰富的“模型视图控制器”Web框架。Spring MVC允许您创建特殊的@Controller
或@RestController
bean来处理传入的HTTP请求。您的控制器中的方法通过使用@RequestMapping
注解映射到HTTP。
以下代码展示了一个典型的提供JSON数据的@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);
}
}
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): User {
return userRepository.findById(userId).get()
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): List<Customer> {
return userRepository.findById(userId).map(customerRepository::findByUser).get()
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long) {
userRepository.deleteById(userId)
}
}
“WebMvc.fn”,即函数式变体,将路由配置与实际请求处理分离,如下例所示:
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun routerFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build()
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
@Component
public class MyUserHandler {
public ServerResponse getUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse getUserCustomers(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse deleteUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
}
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
fun getUserCustomers(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
fun deleteUser(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
}
Spring MVC是核心Spring Framework的一部分,详细信息可在参考文档中找到。还有几个指南涵盖了Spring MVC,可在spring.io/guides上找到。
您可以定义尽可能多的RouterFunction bean,以模块化路由的定义。如果需要应用优先级,可以对bean进行排序。 |
1.1.1. Spring MVC自动配置
Spring Boot为Spring MVC提供了自动配置,适用于大多数应用程序。它取代了@EnableWebMvc
的需要,两者不能同时使用。除了Spring MVC的默认值外,自动配置还提供以下功能:
如果您想保留这些Spring Boot MVC自定义并进行更多的MVC自定义(拦截器、格式化程序、视图控制器和其他功能),可以添加自己的@Configuration
类型的WebMvcConfigurer
类,但不要使用@EnableWebMvc
。
如果您想提供自定义的RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
或ExceptionHandlerExceptionResolver
实例,并仍然保留Spring Boot MVC自定义,可以声明一个类型为WebMvcRegistrations
的bean,并使用它提供这些组件的自定义实例。这些自定义实例将进一步由Spring MVC进行初始化和配置。要参与并在需要时覆盖后续处理,应使用WebMvcConfigurer
。
如果您不想使用自动配置并希望完全控制Spring MVC,可以添加自己的带有@EnableWebMvc
注释的@Configuration
。或者,根据@EnableWebMvc
的Javadoc中描述的方式,添加自己的带有@Configuration
注释的DelegatingWebMvcConfiguration
。
1.1.2. Spring MVC 转换服务
Spring MVC 使用不同的 ConversionService
来转换值,而不是使用在您的 application.properties
或 application.yaml
文件中使用的那个。这意味着 Period
、Duration
和 DataSize
转换器不可用,并且 @DurationUnit
和 @DataSizeUnit
注解将被忽略。
如果您想自定义 Spring MVC 使用的 ConversionService
,您可以提供一个带有 addFormatters
方法的 WebMvcConfigurer
bean。从这个方法中,您可以注册任何您喜欢的转换器,或者您可以委托给 ApplicationConversionService
上可用的静态方法。
还可以使用 spring.mvc.format.*
配置属性来自定义转换。当未配置时,将使用以下默认值:
属性 | DateTimeFormatter |
---|---|
|
|
|
|
|
|
1.1.3. HttpMessageConverters
Spring MVC 使用 HttpMessageConverter
接口来转换 HTTP 请求和响应。默认情况下已包含合理的默认值。例如,对象可以自动转换为 JSON(使用 Jackson 库)或 XML(使用 Jackson XML 扩展,如果可用,或者使用 JAXB 如果 Jackson XML 扩展不可用)。默认情况下,字符串以 UTF-8
编码。
如果需要添加或自定义转换器,可以使用 Spring Boot 的 HttpMessageConverters
类,如下所示:
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
return new HttpMessageConverters(additional, another);
}
}
@Configuration(proxyBeanMethods = false)
class MyHttpMessageConvertersConfiguration {
@Bean
fun customConverters(): HttpMessageConverters {
val additional: HttpMessageConverter<*> = AdditionalHttpMessageConverter()
val another: HttpMessageConverter<*> = AnotherHttpMessageConverter()
return HttpMessageConverters(additional, another)
}
}
任何存在于上下文中的 HttpMessageConverter
bean 都会被添加到转换器列表中。您也可以以相同的方式覆盖默认转换器。
1.1.4. MessageCodesResolver
Spring MVC 有一个用于生成错误代码以从绑定错误中呈现错误消息的策略: MessageCodesResolver
。如果设置了 spring.mvc.message-codes-resolver-format
属性为 PREFIX_ERROR_CODE
或 POSTFIX_ERROR_CODE
,Spring Boot 会为您创建一个(请参阅 DefaultMessageCodesResolver.Format
中的枚举)。
1.1.5. 静态内容
默认情况下,Spring Boot从类路径中名为/static
(或/public
或/resources
或/META-INF/resources
)的目录或从ServletContext
的根目录中提供静态内容。它使用Spring MVC中的ResourceHttpRequestHandler
,因此您可以通过添加自己的WebMvcConfigurer
并覆盖addResourceHandlers
方法来修改该行为。
在独立的Web应用程序中,默认情况下不启用容器中的默认servlet。可以使用server.servlet.register-default-servlet
属性来启用它。
默认servlet充当后备,如果Spring决定不处理内容,则会从ServletContext
的根目录提供内容。大多数情况下,这种情况不会发生(除非您修改默认的MVC配置),因为Spring始终可以通过DispatcherServlet
处理请求。
默认情况下,资源映射在/**
上,但您可以使用spring.mvc.static-path-pattern
属性进行调整。例如,将所有资源重定位到/resources/**
可以通过以下方式实现:
spring.mvc.static-path-pattern=/resources/**
spring:
mvc:
static-path-pattern: "/resources/**"
您还可以使用spring.web.resources.static-locations
属性自定义静态资源位置(将默认值替换为目录位置列表)。根servlet上下文路径"/"
也会自动添加为位置。
除了前面提到的“标准”静态资源位置外,还为Webjars内容制定了特殊情况。默认情况下,任何路径为/webjars/**
的资源如果打包为Webjars格式,则会从jar文件中提供。路径可以使用spring.mvc.webjars-path-pattern
属性进行自定义。
如果您的应用程序打包为jar,请勿使用src/main/webapp 目录。尽管这个目录是一个常见的标准,但它仅适用于war打包,并且如果生成jar,则大多数构建工具会默默忽略它。 |
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 。 |
<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
:
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
资源链接在模板中在运行时被重写,这要归功于为Thymeleaf和FreeMarker自动配置的ResourceUrlEncodingFilter 。在使用JSP时,您应手动声明此过滤器。其他模板引擎目前不受自动支持,但可以通过自定义模板宏/助手和使用ResourceUrlProvider 来支持。 |
当使用JavaScript模块加载器等动态加载资源时,重命名文件不是一个选项。这就是为什么还支持其他策略并且可以组合使用的原因。"fixed"策略在URL中添加静态版本字符串而不更改文件名,如下例所示:
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/js/lib/
spring.web.resources.chain.strategy.fixed.version=v12
spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
fixed:
enabled: true
paths: "/js/lib/"
version: "v12"
使用此配置,位于"/js/lib/"
下的JavaScript模块使用固定版本策略("/v12/js/lib/mymodule.js"
),而其他资源仍使用内容策略(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
)。
有关更多支持的选项,请参阅WebProperties.Resources
。
1.1.6. 欢迎页面
Spring Boot支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找index.html
文件。如果找不到,则会查找index
模板。如果找到任何一个,它将自动用作应用程序的欢迎页面。
这仅作为应用程序定义的实际索引路由的后备。排序由HandlerMapping
bean的顺序定义,默认情况下如下:
|
使用 |
|
在 |
|
欢迎页面支持 |
1.1.8. 路径匹配和内容协商
Spring MVC可以通过查看请求路径并将其与应用程序中定义的映射(例如Controller方法上的@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"):
spring.mvc.contentnegotiation.favor-parameter=true
spring:
mvc:
contentnegotiation:
favor-parameter: true
或者,如果您更喜欢使用不同的参数名称:
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
spring:
mvc:
contentnegotiation:
favor-parameter: true
parameter-name: "myparam"
大多数标准媒体类型都受到开箱即用的支持,但您也可以定义新的媒体类型:
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
spring:
mvc:
contentnegotiation:
media-types:
markdown: "text/markdown"
从Spring Framework 5.3开始,Spring MVC支持两种将请求路径与控制器匹配的策略。默认情况下,Spring Boot使用PathPatternParser策略。PathPatternParser是一个优化实现,但与AntPathMatcher策略相比存在一些限制。PathPatternParser限制了一些路径模式变体的使用。它还与配置DispatcherServlet的路径前缀(spring.mvc.servlet.path)不兼容。
可以使用spring.mvc.pathmatch.matching-strategy配置属性来配置策略,如下例所示:
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
spring:
mvc:
pathmatch:
matching-strategy: "ant-path-matcher"
默认情况下,如果找不到请求的处理程序,Spring MVC将发送404 Not Found错误响应。要改为抛出NoHandlerFoundException,将configprop:spring.mvc.throw-exception-if-no-handler-found设置为true。请注意,默认情况下,静态内容的提供被映射到/**,因此将为所有请求提供处理程序。要抛出NoHandlerFoundException,您还必须将spring.mvc.static-path-pattern设置为更具体的值,例如/resources/**,或将spring.web.resources.add-mappings设置为false以完全禁用静态内容的提供。
1.1.9. ConfigurableWebBindingInitializer
Spring MVC使用WebBindingInitializer为特定请求初始化WebDataBinder。如果您创建自己的ConfigurableWebBindingInitializer @Bean,Spring Boot会自动配置Spring MVC以使用它。
1.1.10. 模板引擎
除了REST Web服务外,您还可以使用Spring MVC提供动态HTML内容。Spring MVC支持各种模板技术,包括Thymeleaf、FreeMarker和JSP。此外,许多其他模板引擎都包含其自己的Spring MVC集成。
Spring Boot包含以下模板引擎的自动配置支持:
如果可能的话,应避免使用JSP。在使用嵌入式servlet容器时,存在一些已知限制。 |
当您使用这些模板引擎与默认配置时,您的模板会自动从src/main/resources/templates中获取。
根据您运行应用程序的方式,您的IDE可能会以不同的顺序排列类路径。在IDE中从其主方法运行应用程序会导致与使用Maven或Gradle运行应用程序或从打包的jar运行应用程序时不同的排序。这可能导致Spring Boot无法找到预期的模板。如果遇到此问题,您可以在IDE中重新排列类路径,将模块的类和资源放在第一位。 |
1.1.11. 错误处理
默认情况下,Spring Boot提供了一个处理所有错误的/error
映射,并将其注册为Servlet容器中的“全局”错误页面。对于机器客户端,它会生成一个包含错误详情、HTTP状态和异常消息的JSON响应。对于浏览器客户端,有一个“whitelabel”错误视图以HTML格式呈现相同的数据(要自定义它,请添加一个解析为error
的View
)。
如果要自定义默认的错误处理行为,可以设置一些server.error
属性。请参阅附录中的“服务器属性”部分。
要完全替换默认行为,可以实现ErrorController
并注册该类型的bean定义,或者添加一个ErrorAttributes
类型的bean以使用现有机制但替换内容。
可以使用BasicErrorController 作为自定义ErrorController 的基类。如果要为新的内容类型添加处理程序(默认情况下是专门处理text/html 并为其他所有内容提供回退),这将特别有用。要实现这一点,扩展BasicErrorController ,添加一个带有produces 属性的@RequestMapping 的公共方法,并创建一个新类型的bean。 |
从Spring Framework 6.0开始,支持RFC 7807问题详情。Spring MVC可以生成带有application/problem+json
媒体类型的自定义错误消息,例如:
{
"type": "https://example.org/problems/unknown-project",
"title": "Unknown project",
"status": 404,
"detail": "No project found for id 'spring-unknown'",
"instance": "/projects/spring-unknown"
}
可以通过将spring.mvc.problemdetails.enabled
设置为true
来启用此支持。
还可以定义一个使用@ControllerAdvice
注解的类,以自定义要为特定控制器和/或异常类型返回的JSON文档,如下例所示:
@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
@ResponseBody
@ExceptionHandler(MyException.class)
public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
HttpStatus status = HttpStatus.resolve(code);
return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
}
}
@ControllerAdvice(basePackageClasses = [SomeController::class])
class MyControllerAdvice : ResponseEntityExceptionHandler() {
@ResponseBody
@ExceptionHandler(MyException::class)
fun handleControllerException(request: HttpServletRequest, ex: Throwable): ResponseEntity<*> {
val status = getStatus(request)
return ResponseEntity(MyErrorBody(status.value(), ex.message), status)
}
private fun getStatus(request: HttpServletRequest): HttpStatus {
val code = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE) as Int
val status = HttpStatus.resolve(code)
return status ?: HttpStatus.INTERNAL_SERVER_ERROR
}
}
在上面的示例中,如果MyException
由与SomeController
相同包中定义的控制器抛出,则使用MyErrorBody
POJO的JSON表示形式,而不是ErrorAttributes
的表示形式。
在某些情况下,控制器级别处理的错误不会被Web观察或度量基础设施记录。应用程序可以通过在观察上下文中设置已处理的异常来确保这些异常被记录。
自定义错误页面
如果要为特定状态代码显示自定义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,如下例所示:
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// 使用请求或状态可选择返回ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// 在此处可以添加自定义模型值
new ModelAndView("myview");
}
return null;
}
}
class MyErrorViewResolver : ErrorViewResolver {
override fun resolveErrorView(request: HttpServletRequest, status: HttpStatus,
model: Map<String, Any>): ModelAndView? {
// 使用请求或状态可选择返回ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// 在此处可以添加自定义模型值
return ModelAndView("myview")
}
return null
}
}
还可以使用常规的Spring MVC功能,如@ExceptionHandler
方法和@ControllerAdvice
。然后ErrorController
会捕获任何未处理的异常。
在Spring MVC之外映射错误页面
对于不使用Spring MVC的应用程序,您可以使用ErrorPageRegistrar
接口直接注册ErrorPages
。这种抽象直接与底层的嵌入式Servlet容器一起工作,即使没有Spring MVC的DispatcherServlet
也可以使用。
@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return this::registerErrorPages;
}
private void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}
@Configuration(proxyBeanMethods = false)
class MyErrorPagesConfiguration {
@Bean
fun errorPageRegistrar(): ErrorPageRegistrar {
return ErrorPageRegistrar { registry: ErrorPageRegistry -> registerErrorPages(registry) }
}
private fun registerErrorPages(registry: ErrorPageRegistry) {
registry.addErrorPages(ErrorPage(HttpStatus.BAD_REQUEST, "/400"))
}
}
如果您注册的ErrorPage 的路径最终由一个Filter 处理(这在一些非Spring Web框架(如Jersey和Wicket)中很常见),那么Filter 必须显式注册为ERROR 分发器,如下例所示: |
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
}
@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {
@Bean
fun myFilter(): FilterRegistrationBean<MyFilter> {
val registration = FilterRegistrationBean(MyFilter())
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType::class.java))
return registration
}
}
请注意,默认的FilterRegistrationBean
不包括ERROR
分发器类型。
在WAR部署中的错误处理
当部署到Servlet容器时,Spring Boot使用其错误页面过滤器将具有错误状态的请求转发到适当的错误页面。这是必要的,因为Servlet规范没有提供用于注册错误页面的API。根据部署WAR文件的容器和应用程序使用的技术,可能需要一些额外的配置。
错误页面过滤器只能在响应尚未提交时将请求转发到正确的错误页面。默认情况下,WebSphere Application Server 8.0及更高版本在Servlet的service方法成功完成时会提交响应。您应该通过将com.ibm.ws.webcontainer.invokeFlushAfterService
设置为false
来禁用此行为。
1.1.12. 跨域资源共享支持
从4.2版本开始,Spring MVC支持CORS。在您的Spring Boot应用程序中使用控制器方法CORS配置与@CrossOrigin
注解不需要任何特定配置。全局CORS配置可以通过注册一个带有自定义addCorsMappings(CorsRegistry)
方法的WebMvcConfigurer
bean来定义,如下例所示:
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
}
}
}
}
1.2. JAX-RS和Jersey
如果您更喜欢使用JAX-RS编程模型来构建REST端点,您可以使用可用的实现之一,而不是Spring MVC。 Jersey 和 Apache CXF 都可以直接使用。 CXF要求您在应用程序上下文中将其Servlet
或Filter
注册为@Bean
。 Jersey具有一些原生的Spring支持,因此我们在Spring Boot中为其提供了自动配置支持,以及一个starter。
要开始使用Jersey,请将spring-boot-starter-jersey
包含为依赖项,然后您需要一个@Bean
类型为ResourceConfig
的对象,在其中注册所有端点,如下例所示:
@Component
public class MyJerseyConfig extends ResourceConfig {
public MyJerseyConfig() {
register(MyEndpoint.class);
}
}
Jersey对扫描可执行存档的支持相当有限。例如,它无法扫描在fully executable jar file 中找到的包中的端点,或者在运行可执行war文件时无法扫描WEB-INF/classes 中的端点。为避免此限制,不应使用packages 方法,而应通过使用register 方法逐个注册端点,如前面的示例所示。 |
对于更高级的自定义,您还可以注册任意数量实现ResourceConfigCustomizer
的bean。
所有注册的端点应为带有HTTP资源注解(@GET
等)的@Components
,如下例所示:
@Component
@Path("/hello")
public class MyEndpoint {
@GET
public String message() {
return "Hello";
}
}
由于Endpoint
是Spring的@Component
,其生命周期由Spring管理,您可以使用@Autowired
注解来注入依赖项,并使用@Value
注解来注入外部配置。默认情况下,Jersey servlet被注册并映射到/*
。您可以通过向您的ResourceConfig
添加@ApplicationPath
来更改映射。
默认情况下,Jersey设置为@Bean
类型为ServletRegistrationBean
的servlet,名称为jerseyServletRegistration
。默认情况下,servlet是懒初始化的,但您可以通过设置spring.jersey.servlet.load-on-startup
来自定义该行为。您可以通过创建一个同名的bean来禁用或覆盖该bean。您还可以通过设置spring.jersey.type=filter
来使用过滤器而不是servlet(在这种情况下,要替换或覆盖的@Bean
是jerseyFilterRegistration
)。过滤器具有一个@Order
,您可以使用spring.jersey.filter.order
来设置。当使用Jersey作为过滤器时,必须存在一个servlet来处理Jersey未拦截的任何请求。如果您的应用程序不包含这样的servlet,则可能希望通过将server.servlet.register-default-servlet
设置为true
来启用默认servlet。可以使用spring.jersey.init.*
来为servlet和过滤器注册提供初始化参数,以指定属性映射。
1.3. 嵌入式Servlet容器支持
对于Servlet应用程序,Spring Boot包含对嵌入式Tomcat,Jetty和Undertow服务器的支持。大多数开发人员使用适当的“Starter”来获取一个完全配置的实例。默认情况下,嵌入式服务器在端口8080
上监听HTTP请求。
1.3.1. Servlets、Filters和Listeners
在使用嵌入式servlet容器时,您可以注册servlets、filters和所有监听器(如HttpSessionListener
)从servlet规范中,可以通过使用Spring beans或扫描servlet组件来实现。
将Servlets、Filters和Listeners注册为Spring Beans
任何作为Spring bean的Servlet
、Filter
或servlet *Listener
实例都会被注册到嵌入式容器中。如果您想在配置过程中引用application.properties
中的值,这可能特别方便。
默认情况下,如果上下文只包含一个Servlet,则将其映射到/
。在存在多个servlet bean的情况下,将使用bean名称作为路径前缀。Filters映射到/*
。
如果基于约定的映射不够灵活,您可以使用ServletRegistrationBean
、FilterRegistrationBean
和ServletListenerRegistrationBean
类来完全控制。
通常情况下,将过滤器bean无序化是安全的。如果需要特定顺序,应该使用@Order
对Filter
进行注解或使其实现Ordered
。您不能通过在其bean方法上注解@Order
来配置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模式,将在启动时记录。 |
在注册Filter bean时要小心,因为它们在应用程序生命周期的早期就被初始化。如果需要注册一个与其他bean交互的Filter ,考虑使用DelegatingFilterProxyRegistrationBean 。 |
1.3.2. 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
的适配器。
1.3.3. ServletWebServerApplicationContext
在幕后,Spring Boot为嵌入式servlet容器支持使用了一种不同类型的ApplicationContext
。ServletWebServerApplicationContext
是一种特殊类型的WebApplicationContext
,通过搜索单个ServletWebServerFactory
bean来引导自身。通常会自动配置一个TomcatServletWebServerFactory
、JettyServletWebServerFactory
或UndertowServletWebServerFactory
。
通常情况下,您不需要了解这些实现类。大多数应用程序都是自动配置的,并且适当的ApplicationContext 和ServletWebServerFactory 会代表您创建。 |
在嵌入式容器设置中,ServletContext
是在服务器启动过程中设置的,这发生在应用程序上下文初始化期间。因此,在ApplicationContext
中的bean不能可靠地使用ServletContext
进行初始化。解决此问题的一种方法是将ApplicationContext
作为bean的依赖注入,并且仅在需要时访问ServletContext
。另一种方法是在服务器启动后使用回调。这可以通过一个ApplicationListener
来实现,该监听器监听ApplicationStartedEvent
,如下所示:
public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {
private ServletContext servletContext;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
}
}
1.3.4. 自定义嵌入式Servlet容器
可以使用Spring Environment
属性配置常见的servlet容器设置。通常,您会在application.properties
或application.yaml
文件中定义这些属性。
常见的服务器设置包括:
Spring Boot尽可能地暴露常见设置,但这并非总是可能的。对于这些情况,专用的命名空间提供了特定于服务器的自定义(参见server.tomcat
和server.undertow
)。例如,可以使用特定于嵌入式servlet容器的特定功能来配置访问日志。
查看ServerProperties 类以获取完整列表。 |
SameSite Cookies
SameSite
cookie属性可被Web浏览器用于控制在跨站点请求中如何提交cookie。该属性对于现代Web浏览器特别重要,这些浏览器已经开始更改当属性缺失时使用的默认值。
如果要更改会话cookie的SameSite
属性,可以使用server.servlet.session.cookie.same-site
属性。此属性受到自动配置的Tomcat、Jetty和Undertow服务器支持。它还用于配置基于Spring Session servlet的SessionRepository
bean。
例如,如果希望会话cookie具有SameSite
属性为None
,可以将以下内容添加到您的application.properties
或application.yaml
文件中:
server.servlet.session.cookie.same-site=none
server:
servlet:
session:
cookie:
same-site: "none"
如果要更改添加到HttpServletResponse
的其他cookie的SameSite
属性,可以使用CookieSameSiteSupplier
。CookieSameSiteSupplier
接收一个Cookie
,可以返回一个SameSite
值或null
。
有许多便利的工厂和过滤器方法可用于快速匹配特定cookie。例如,添加以下bean将自动为所有名称与正则表达式myapp.*
匹配的cookie应用SameSite
为Lax
。
@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
@Bean
public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
}
}
@Configuration(proxyBeanMethods = false)
class MySameSiteConfiguration {
@Bean
fun applicationCookieSameSiteSupplier(): CookieSameSiteSupplier {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*")
}
}
字符编码
可以使用server.servlet.encoding.*
配置属性来配置嵌入式servlet容器用于请求和响应处理的字符编码行为。
当请求的Accept-Language
头指示请求的区域设置时,servlet容器将自动将其映射到字符集。每个容器提供默认的区域设置到字符集映射,您应验证它们是否符合应用程序的需求。当不符合时,使用server.servlet.encoding.mapping
配置属性来自定义映射,如下例所示:
server.servlet.encoding.mapping.ko=UTF-8
server:
servlet:
encoding:
mapping:
ko: "UTF-8"
在上面的示例中,将韩文(ko
)区域设置映射到UTF-8
。这相当于传统war部署的web.xml
文件中的<locale-encoding-mapping-list>
条目。
编程定制
如果您需要以编程方式配置嵌入式Servlet容器,可以注册一个实现WebServerFactoryCustomizer
接口的Spring bean。WebServerFactoryCustomizer
提供对ConfigurableServletWebServerFactory
的访问,其中包括许多自定义setter方法。以下示例展示了如何以编程方式设置端口:
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
override fun customize(server: ConfigurableServletWebServerFactory) {
server.setPort(9000)
}
}
TomcatServletWebServerFactory
、JettyServletWebServerFactory
和UndertowServletWebServerFactory
是ConfigurableServletWebServerFactory
的专用变体,分别具有用于Tomcat、Jetty和Undertow的额外自定义setter方法。以下示例展示了如何自定义TomcatServletWebServerFactory
,以提供对Tomcat特定配置选项的访问:
@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory server) {
server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
}
}
@Component
class MyTomcatWebServerFactoryCustomizer : WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
override fun customize(server: TomcatServletWebServerFactory) {
server.addConnectorCustomizers({ connector -> connector.asyncTimeout = Duration.ofSeconds(20).toMillis() })
}
}
直接定制ConfigurableServletWebServerFactory
对于需要从ServletWebServerFactory
扩展的更高级用例,您可以自行公开此类型的bean。
提供了许多配置选项的setter。如果需要执行更多特殊操作,还提供了几个受保护的方法“钩子”。有关详细信息,请参阅源代码文档。
自动配置的自定义器仍然会应用于您的自定义工厂,因此请谨慎使用该选项。 |
2. 响应式Web应用程序
Spring Boot通过为Spring Webflux提供自动配置简化了响应式Web应用程序的开发。
2.1. “Spring WebFlux Framework”
Spring WebFlux是在Spring Framework 5.0中引入的新的响应式Web框架。与Spring MVC不同,它不需要servlet API,完全是异步和非阻塞的,并通过Reactive Streams规范实现,通过Reactor项目。
Spring WebFlux有两种风格:函数式和基于注解的。基于注解的风格与Spring MVC模型非常接近,如下例所示:
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public Mono<User> getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId);
}
@GetMapping("/{userId}/customers")
public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
}
@DeleteMapping("/{userId}")
public Mono<Void> deleteUser(@PathVariable Long userId) {
return this.userRepository.deleteById(userId);
}
}
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): Mono<User?> {
return userRepository.findById(userId)
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): Flux<Customer> {
return userRepository.findById(userId).flatMapMany { user: User? ->
customerRepository.findByUser(user)
}
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long): Mono<Void> {
return userRepository.deleteById(userId)
}
}
WebFlux是Spring Framework的一部分,详细信息可在其参考文档中找到。
“WebFlux.fn”,即函数式变体,将路由配置与实际请求处理分离,如下例所示:
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun monoRouterFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route(
GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute(
GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute(
DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser)
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
@Component
public class MyUserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
...
}
}
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
}
“WebFlux.fn”是Spring Framework的一部分,详细信息可在其参考文档中找到。
您可以定义尽可能多的RouterFunction bean,以模块化路由的定义。如果需要应用优先级,可以对bean进行排序。 |
要开始使用,将spring-boot-starter-webflux
模块添加到您的应用程序中。
在应用程序中同时添加spring-boot-starter-web 和spring-boot-starter-webflux 模块会导致Spring Boot自动配置Spring MVC,而不是WebFlux。选择这种行为是因为许多Spring开发人员将spring-boot-starter-webflux 添加到他们的Spring MVC应用程序中以使用响应式WebClient 。您仍然可以通过将所选应用程序类型设置为SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE) 来强制执行您的选择。 |
2.1.1. Spring WebFlux自动配置
Spring Boot为Spring WebFlux提供了自动配置,适用于大多数应用程序。
自动配置在Spring的默认基础上添加了以下功能:
如果您想保留Spring Boot WebFlux功能,并且想要添加额外的WebFlux配置,您可以添加自己的@Configuration
类,类型为WebFluxConfigurer
,但不要使用@EnableWebFlux
。
如果您想完全控制Spring WebFlux,可以添加自己的@Configuration
,并使用@EnableWebFlux
进行注释。
2.1.2. Spring WebFlux 转换服务
如果您想自定义Spring WebFlux使用的ConversionService
,您可以提供一个带有addFormatters
方法的WebFluxConfigurer
bean。
还可以使用spring.webflux.format.*
配置属性来自定义转换。当未配置时,将使用以下默认值:
属性 | DateTimeFormatter |
---|---|
|
|
|
|
|
|
2.1.3. 使用HttpMessageReaders和HttpMessageWriters的HTTP编解码器
Spring WebFlux使用HttpMessageReader
和HttpMessageWriter
接口来转换HTTP请求和响应。它们通过CodecConfigurer
进行配置,通过查看类路径中可用的库来设置合理的默认值。
Spring Boot为编解码器提供了专用的配置属性,spring.codec.*
。它还通过使用CodecCustomizer
实例进行进一步的自定义。例如,spring.jackson.*
配置键适用于Jackson编解码器。
如果需要添加或自定义编解码器,可以创建一个自定义的CodecCustomizer
组件,如下例所示:
@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {
@Bean
public CodecCustomizer myCodecCustomizer() {
return (configurer) -> {
configurer.registerDefaults(false);
configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
// ...
};
}
}
class MyCodecsConfiguration {
@Bean
fun myCodecCustomizer(): CodecCustomizer {
return CodecCustomizer { configurer: CodecConfigurer ->
configurer.registerDefaults(false)
configurer.customCodecs().register(ServerSentEventHttpMessageReader())
}
}
}
您还可以利用Boot的自定义JSON序列化器和反序列化器。
2.1.4. 静态内容
默认情况下,Spring Boot从类路径中名为/static
(或/public
或/resources
或/META-INF/resources
)的目录中提供静态内容。它使用Spring WebFlux的ResourceWebHandler
,因此您可以通过添加自己的WebFluxConfigurer
并覆盖addResourceHandlers
方法来修改该行为。
默认情况下,资源映射在/**
上,但您可以通过设置spring.webflux.static-path-pattern
属性来调整。例如,将所有资源重定位到/resources/**
可以通过以下方式实现:
spring.webflux.static-path-pattern=/resources/**
spring:
webflux:
static-path-pattern: "/resources/**"
您还可以使用spring.web.resources.static-locations
来自定义静态资源位置。这样会用自定义位置列表替换默认值。如果这样做,默认的欢迎页面检测将切换到您的自定义位置。因此,如果在任何启动位置中有一个index.html
,它将成为应用程序的主页。
除了之前列出的“标准”静态资源位置外,还为Webjars内容做了特殊处理。默认情况下,任何路径为/webjars/**
的资源如果打包为Webjars格式,则会从jar文件中提供。路径可以通过spring.webflux.webjars-path-pattern
属性进行自定义。
Spring WebFlux应用程序不严格依赖于servlet API,因此不能作为war文件部署,也不使用src/main/webapp 目录。 |
2.1.5. 欢迎页面
Spring Boot支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找index.html
文件。如果找不到,则查找index
模板。如果找到任何一个,它将自动用作应用程序的欢迎页面。
这仅作为应用程序定义的实际索引路由的后备。排序由HandlerMapping
bean的顺序定义,默认情况下如下:
|
使用 |
|
在 |
|
欢迎页面支持 |
2.1.6. 模板引擎
除了REST Web服务外,您还可以使用Spring WebFlux提供动态HTML内容。Spring WebFlux支持各种模板技术,包括Thymeleaf、FreeMarker和Mustache。
Spring Boot包含以下模板引擎的自动配置支持:
当您使用这些模板引擎与默认配置时,模板会自动从src/main/resources/templates
中获取。
2.1.7. 错误处理
Spring Boot提供了一个WebExceptionHandler
,以合理的方式处理所有错误。它在处理顺序中的位置位于WebFlux提供的处理程序之前,被认为是最后一个。对于机器客户端,它生成一个带有错误详细信息、HTTP状态和异常消息的JSON响应。对于浏览器客户端,有一个“whitelabel”错误处理程序以HTML格式呈现相同的数据。您还可以提供自己的HTML模板来显示错误(请参阅下一节)。
在直接定制Spring Boot中的错误处理之前,您可以利用Spring WebFlux中的RFC 7807问题详细信息支持。Spring WebFlux可以生成带有application/problem+json
媒体类型的自定义错误消息,例如:
{
"type": "https://example.org/problems/unknown-project",
"title": "Unknown project",
"status": 404,
"detail": "No project found for id 'spring-unknown'",
"instance": "/projects/spring-unknown"
}
可以通过将spring.webflux.problemdetails.enabled
设置为true
来启用此支持。
定制此功能的第一步通常涉及使用现有机制但替换或增强错误内容。为此,您可以添加一个ErrorAttributes
类型的bean。
要更改错误处理行为,您可以实现ErrorWebExceptionHandler
并注册该类型的bean定义。由于ErrorWebExceptionHandler
是相当低级的,Spring Boot还提供了一个方便的AbstractErrorWebExceptionHandler
,让您以WebFlux功能方式处理错误,如下例所示:
@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();
}
}
@Component
class MyErrorWebExceptionHandler(
errorAttributes: ErrorAttributes, webProperties: WebProperties,
applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer
) : AbstractErrorWebExceptionHandler(errorAttributes, webProperties.resources, applicationContext) {
init {
setMessageReaders(serverCodecConfigurer.readers)
setMessageWriters(serverCodecConfigurer.writers)
}
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
}
private fun acceptsXml(request: ServerRequest): Boolean {
return request.headers().accept().contains(MediaType.APPLICATION_XML)
}
fun handleErrorAsXml(request: ServerRequest): Mono<ServerResponse> {
val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
// ... additional builder calls
return builder.build()
}
}
为了更全面地了解情况,您还可以直接子类化DefaultErrorWebExceptionHandler
并覆盖特定方法。
在某些情况下,在控制器级别处理的错误不会被Web观察或度量基础设施记录。应用程序可以通过在观察上下文中设置已处理的异常来确保这些异常被记录。
自定义错误页面
如果要为给定状态代码显示自定义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>
2.2. 嵌入式响应式服务器支持
Spring Boot包含对以下嵌入式响应式Web服务器的支持:Reactor Netty、Tomcat、Jetty和Undertow。大多数开发人员使用适当的“Starter”来获取一个完全配置的实例。默认情况下,嵌入式服务器在端口8080上监听HTTP请求。
2.2.1. 自定义响应式服务器
可以使用Spring Environment
属性配置常见的响应式Web服务器设置。通常,您会在application.properties
或application.yaml
文件中定义这些属性。
常见的服务器设置包括:
Spring Boot尽可能地暴露常见设置,但这并非总是可能的。对于这些情况,专用的命名空间(如server.netty.*
)提供了服务器特定的自定义。
查看完整列表,请参阅ServerProperties 类。 |
编程自定义
如果需要以编程方式配置您的响应式Web服务器,可以注册一个实现WebServerFactoryCustomizer
接口的Spring bean。WebServerFactoryCustomizer
提供对ConfigurableReactiveWebServerFactory
的访问,其中包括许多自定义setter方法。以下示例展示了如何以编程方式设置端口:
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {
@Override
public void customize(ConfigurableReactiveWebServerFactory server) {
server.setPort(9000);
}
}
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {
override fun customize(server: ConfigurableReactiveWebServerFactory) {
server.setPort(9000)
}
}
JettyReactiveWebServerFactory
、NettyReactiveWebServerFactory
、TomcatReactiveWebServerFactory
和UndertowReactiveWebServerFactory
是ConfigurableReactiveWebServerFactory
的专用变体,分别具有用于Jetty、Reactor Netty、Tomcat和Undertow的附加自定义setter方法。以下示例展示了如何自定义NettyReactiveWebServerFactory
,该工厂提供了对Reactor Netty特定配置选项的访问:
@Component
public class MyNettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
@Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers((server) -> server.idleTimeout(Duration.ofSeconds(20)));
}
}
@Component
class MyNettyWebServerFactoryCustomizer : WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
override fun customize(factory: NettyReactiveWebServerFactory) {
factory.addServerCustomizers({ server -> server.idleTimeout(Duration.ofSeconds(20)) })
}
}
直接自定义ConfigurableReactiveWebServerFactory
对于需要从ReactiveWebServerFactory
扩展的更高级用例,可以自行公开此类型的bean。
提供了许多配置选项的setter。还提供了一些受保护的方法“钩子”,以便在需要执行更多特殊操作时使用。有关详细信息,请参阅源代码文档。
自动配置的自定义器仍然会应用于您的自定义工厂,因此请谨慎使用该选项。 |
2.3. 响应式服务器资源配置
在自动配置Reactor Netty或Jetty服务器时,Spring Boot将创建特定的bean,这些bean将为服务器实例提供HTTP资源:ReactorResourceFactory
或JettyResourceFactory
。
默认情况下,这些资源也将与Reactor Netty和Jetty客户端共享,以实现最佳性能,因为:
-
服务器和客户端使用相同的技术
-
客户端实例是使用Spring Boot自动配置的
WebClient.Builder
构建的
开发人员可以通过提供自定义的ReactorResourceFactory
或JettyResourceFactory
bean来覆盖Jetty和Reactor Netty的资源配置 - 这将应用于客户端和服务器。
您可以在WebClient运行时部分了解有关客户端端资源配置的更多信息。
3. 优雅关闭
所有四个内嵌的Web服务器(Jetty、Reactor Netty、Tomcat和Undertow)都支持优雅关闭,适用于响应式和基于Servlet的Web应用程序。它作为关闭应用程序上下文的一部分发生,并在停止SmartLifecycle
bean 的最早阶段执行。此停止处理使用超时,提供了一个宽限期,在此期间现有请求将被允许完成,但不会允许新请求。不允许新请求的确切方式因所使用的Web服务器而异。Jetty、Reactor Netty和Tomcat将在网络层停止接受请求。Undertow将接受请求,但立即用服务不可用(503)响应。
使用Tomcat进行优雅关闭需要Tomcat 9.0.33或更高版本。 |
要启用优雅关闭,请配置server.shutdown
属性,如以下示例所示:
server.shutdown=graceful
server:
shutdown: "graceful"
要配置超时期限,请配置spring.lifecycle.timeout-per-shutdown-phase
属性,如以下示例所示:
spring.lifecycle.timeout-per-shutdown-phase=20s
spring:
lifecycle:
timeout-per-shutdown-phase: "20s"
如果IDE未发送正确的SIGTERM 信号,则使用IDE进行优雅关闭可能无法正常工作。有关更多详细信息,请参阅您的IDE文档。 |
4. Spring安全
如果在类路径上存在Spring Security,则Web应用程序默认受到保护。Spring Boot依赖于Spring Security的内容协商策略来确定是否使用httpBasic
或formLogin
。要向Web应用程序添加方法级安全性,还可以添加@EnableGlobalMethodSecurity
并使用所需的设置。更多信息可以在Spring Security参考指南中找到。
默认的UserDetailsService
有一个用户。用户名为user
,密码是随机生成的,并在应用程序启动时以WARN级别打印,如下例所示:
Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35 此生成的密码仅用于开发目的。在将应用程序部署到生产环境之前,必须更新您的安全配置。
如果调整日志配置,请确保org.springframework.boot.autoconfigure.security 类别设置为记录WARN 级别的消息。否则,默认密码将不会被打印。 |
您可以通过提供spring.security.user.name
和spring.security.user.password
来更改用户名和密码。
Web应用程序默认提供的基本功能包括:
-
具有内存存储和单个用户的
UserDetailsService
(或在WebFlux应用程序中为ReactiveUserDetailsService
)bean,并具有生成的密码(有关用户属性,请参阅SecurityProperties.User
)。 -
基于表单的登录或HTTP基本安全性(取决于请求中的
Accept
标头)适用于整个应用程序(包括如果actuator在类路径上则包括actuator端点)。 -
用于发布身份验证事件的
DefaultAuthenticationEventPublisher
。
您可以通过添加一个bean来提供不同的AuthenticationEventPublisher
。
4.1. MVC安全性
默认的安全配置实现在SecurityAutoConfiguration
和UserDetailsServiceAutoConfiguration
中。 SecurityAutoConfiguration
导入SpringBootWebSecurityConfiguration
用于Web安全性,UserDetailsServiceAutoConfiguration
配置身份验证,这在非Web应用程序中也是相关的。
要完全关闭默认的Web应用程序安全配置,或者要组合多个Spring Security组件(如OAuth2客户端和资源服务器),请添加一个SecurityFilterChain
类型的bean(这样做不会禁用UserDetailsService
配置或Actuator的安全性)。要同时关闭UserDetailsService
配置,可以添加一个UserDetailsService
、AuthenticationProvider
或AuthenticationManager
类型的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提供了方便的方法,可用于覆盖actuator端点和静态资源的访问规则。 EndpointRequest
可用于创建基于management.endpoints.web.base-path
属性的RequestMatcher
。 PathRequest
可用于为常用位置中的资源创建RequestMatcher
。
4.2. WebFlux安全性
与Spring MVC应用程序类似,您可以通过添加spring-boot-starter-security
依赖项来保护您的WebFlux应用程序。默认的安全配置实现在ReactiveSecurityAutoConfiguration
和UserDetailsServiceAutoConfiguration
中。 ReactiveSecurityAutoConfiguration
导入WebFluxSecurityConfiguration
用于Web安全性,UserDetailsServiceAutoConfiguration
配置身份验证,这在非Web应用程序中也是相关的。
要完全关闭默认的Web应用程序安全配置,可以添加一个WebFilterChainProxy
类型的bean(这样做不会禁用UserDetailsService
配置或Actuator的安全性)。要同时关闭UserDetailsService
配置,可以添加一个ReactiveUserDetailsService
或ReactiveAuthenticationManager
类型的bean。
当类路径上存在以下任何Spring Security模块时,自动配置也会进行回退:
-
spring-security-oauth2-client
-
spring-security-oauth2-resource-server
要在这些依赖项及更多依赖项中使用ReactiveUserDetailsService
,请定义自己的MapReactiveUserDetailsService
bean。
可以通过添加自定义的SecurityWebFilterChain
bean来配置访问规则和使用多个Spring Security组件,如OAuth 2客户端和资源服务器。Spring Boot提供了方便的方法,可用于覆盖actuator端点和静态资源的访问规则。 EndpointRequest
可用于创建基于management.endpoints.web.base-path
属性的ServerWebExchangeMatcher
。
PathRequest
可用于为常用位置中的资源创建ServerWebExchangeMatcher
。
例如,您可以通过添加以下内容来自定义您的安全配置:
@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange((exchange) -> {
exchange.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
exchange.pathMatchers("/foo", "/bar").authenticated();
});
http.formLogin(withDefaults());
return http.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyWebFluxSecurityConfiguration {
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http.authorizeExchange { spec ->
spec.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
spec.pathMatchers("/foo", "/bar").authenticated()
}
http.formLogin(withDefaults())
return http.build()
}
}
4.3. OAuth2
OAuth2 是一个广泛使用的授权框架,Spring 支持该框架。
4.3.1. 客户端
如果您的类路径上有spring-security-oauth2-client
,您可以利用一些自动配置来设置OAuth2/Open ID Connect客户端。此配置利用了OAuth2ClientProperties
下的属性。这些属性同样适用于Servlet和响应式应用程序。
您可以在spring.security.oauth2.client
前缀下注册多个OAuth2客户端和提供者,如下例所示:
spring.security.oauth2.client.registration.my-login-client.client-id=abcd
spring.security.oauth2.client.registration.my-login-client.client-secret=password
spring.security.oauth2.client.registration.my-login-client.client-name=用于OpenID Connect的客户端
spring.security.oauth2.client.registration.my-login-client.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-login-client.scope=openid,profile,email,phone,address
spring.security.oauth2.client.registration.my-login-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.my-login-client.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-login-client.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=用于用户范围的客户端
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri={baseUrl}/authorized/user
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=用于电子邮件范围的客户端
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri={baseUrl}/authorized/email
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server.com/oauth2/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server.com/oauth2/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server.com/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server.com/oauth2/jwks
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
spring:
security:
oauth2:
client:
registration:
my-login-client:
client-id: "abcd"
client-secret: "password"
client-name: "用于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: "用于用户范围的客户端"
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: "用于电子邮件范围的客户端"
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发现的提供者,配置可以进一步简化。提供者需要配置一个issuer-uri
,这是它断言为其发行者标识符的URI。例如,如果提供的issuer-uri
是"https://example.com",那么将向"https://example.com/.well-known/openid-configuration"发出"OpenID提供者配置请求"。预期结果是一个"OpenID提供者配置响应"。以下示例显示了如何配置OpenID Connect提供者的issuer-uri
:
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
spring:
security:
oauth2:
client:
provider:
oidc-provider:
issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"
默认情况下,Spring Security的OAuth2LoginAuthenticationFilter
仅处理与/login/oauth2/code/*
匹配的URL。如果要自定义redirect-uri
以使用不同的模式,您需要提供配置来处理该自定义模式。例如,对于Servlet应用程序,您可以添加类似以下内容的自定义SecurityFilterChain
:
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MyOAuthClientConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated()
)
.oauth2Login((login) -> login
.redirectionEndpoint((endpoint) -> endpoint
.baseUri("/login/oauth2/callback/*")
)
);
return http.build();
}
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
open class MyOAuthClientConfiguration {
@Bean
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
oauth2Login {
redirectionEndpoint {
baseUri = "/login/oauth2/callback/*"
}
}
}
return http.build()
}
}
Spring Boot自动配置了一个InMemoryOAuth2AuthorizedClientService ,Spring Security用于管理客户端注册。InMemoryOAuth2AuthorizedClientService 的功能有限,我们建议仅在开发环境中使用它。对于生产环境,请考虑使用JdbcOAuth2AuthorizedClientService 或创建自己的OAuth2AuthorizedClientService 实现。 |
常见提供商的OAuth2客户端注册
对于常见的OAuth2和OpenID提供商,包括Google、Github、Facebook和Okta,我们提供了一组提供商默认值(分别为google
、github
、facebook
和okta
)。
如果您不需要自定义这些提供商,可以将provider
属性设置为您需要推断默认值的提供商。此外,如果客户端注册的键与默认支持的提供商匹配,Spring Boot也会推断。
换句话说,在以下示例中,两个配置使用了Google提供商:
spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password
spring:
security:
oauth2:
client:
registration:
my-client:
client-id: "abcd"
client-secret: "password"
provider: "google"
google:
client-id: "abcd"
client-secret: "password"
4.3.2. 资源服务器
如果在类路径上有spring-security-oauth2-resource-server
,Spring Boot可以设置一个OAuth2资源服务器。对于JWT配置,需要指定JWK Set URI或OIDC Issuer URI,如下例所示:
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: "https://example.com/oauth2/default/v1/keys"
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"
如果授权服务器不支持JWK Set URI,您可以使用用于验证JWT签名的公钥配置资源服务器。这可以通过spring.security.oauth2.resourceserver.jwt.public-key-location 属性完成,其中值需要指向包含PEM编码x509格式公钥的文件。 |
spring.security.oauth2.resourceserver.jwt.audiences
属性可用于指定JWT中aud声明的预期值。例如,要求JWT包含值为my-audience
的aud声明:
spring.security.oauth2.resourceserver.jwt.audiences[0]=my-audience
spring:
security:
oauth2:
resourceserver:
jwt:
audiences:
- "my-audience"
相同的属性适用于Servlet和响应式应用程序。或者,您可以为Servlet应用程序定义自己的JwtDecoder
bean,或者为响应式应用程序定义ReactiveJwtDecoder
。
在使用不是JWT而是不透明令牌的情况下,您可以配置以下属性通过内省来验证令牌:
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://example.com/check-token
spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id
spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret
spring:
security:
oauth2:
resourceserver:
opaquetoken:
introspection-uri: "https://example.com/check-token"
client-id: "my-client-id"
client-secret: "my-client-secret"
同样,这些属性适用于Servlet和响应式应用程序。或者,您可以为Servlet应用程序定义自己的OpaqueTokenIntrospector
bean,或者为响应式应用程序定义ReactiveOpaqueTokenIntrospector
。
4.3.3. 授权服务器
如果在类路径上有spring-security-oauth2-authorization-server
,您可以利用一些自动配置来设置基于Servlet的OAuth2授权服务器。
您可以在spring.security.oauth2.authorizationserver.client
前缀下注册多个OAuth2客户端,如下例所示:
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-id=abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-secret={noop}secret1
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-authentication-methods[0]=client_secret_basic
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[0]=authorization_code
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[1]=refresh_token
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[0]=https://my-client-1.com/login/oauth2/code/abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[1]=https://my-client-1.com/authorized
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[0]=openid
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[1]=profile
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[2]=email
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[3]=phone
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[4]=address
spring.security.oauth2.authorizationserver.client.my-client-1.require-authorization-consent=true
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-id=efgh
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-secret={noop}secret2
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-authentication-methods[0]=client_secret_jwt
spring.security.oauth2.authorizationserver.client.my-client-2.registration.authorization-grant-types[0]=client_credentials
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[0]=user.read
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[1]=user.write
spring.security.oauth2.authorizationserver.client.my-client-2.jwk-set-uri=https://my-client-2.com/jwks
spring.security.oauth2.authorizationserver.client.my-client-2.token-endpoint-authentication-signing-algorithm=RS256
spring:
security:
oauth2:
authorizationserver:
client:
my-client-1:
registration:
client-id: "abcd"
client-secret: "{noop}secret1"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
- "refresh_token"
redirect-uris:
- "https://my-client-1.com/login/oauth2/code/abcd"
- "https://my-client-1.com/authorized"
scopes:
- "openid"
- "profile"
- "email"
- "phone"
- "address"
require-authorization-consent: true
my-client-2:
registration:
client-id: "efgh"
client-secret: "{noop}secret2"
client-authentication-methods:
- "client_secret_jwt"
authorization-grant-types:
- "client_credentials"
scopes:
- "user.read"
- "user.write"
jwk-set-uri: "https://my-client-2.com/jwks"
token-endpoint-authentication-signing-algorithm: "RS256"
必须以可以与配置的PasswordEncoder 匹配的格式提供client-secret 属性。默认的PasswordEncoder 实例是通过PasswordEncoderFactories.createDelegatingPasswordEncoder() 创建的。 |
Spring Boot为Spring授权服务器提供的自动配置旨在快速入门。大多数应用程序将需要自定义,并希望定义多个bean来覆盖自动配置。
以下组件可以定义为bean,以覆盖特定于Spring授权服务器的自动配置:
-
RegisteredClientRepository
-
AuthorizationServerSettings
-
SecurityFilterChain
-
com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext>
-
JwtDecoder
Spring Boot自动配置了一个InMemoryRegisteredClientRepository ,Spring授权服务器用于管理注册的客户端。InMemoryRegisteredClientRepository 的功能有限,我们建议仅在开发环境中使用。对于生产环境,请考虑使用JdbcRegisteredClientRepository 或创建自己的RegisteredClientRepository 实现。 |
更多信息请参阅Spring授权服务器参考指南中的入门章节。
4.4. SAML 2.0
4.4.1. 依赖方
如果在类路径中有spring-security-saml2-service-provider
,您可以利用一些自动配置来设置 SAML 2.0 依赖方。此配置利用了Saml2RelyingPartyProperties
下的属性。
依赖方注册表示标识提供方(IDP)和服务提供方(SP)之间配对配置。您可以在spring.security.saml2.relyingparty
前缀下注册多个依赖方,如下例所示:
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=私钥路径
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=证书路径
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=私钥路径
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=证书路径
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=验证证书路径
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.entity-id=远程IDP实体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=私钥路径
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=证书路径
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=私钥路径
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=证书路径
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.verification.credentials[0].certificate-location=其他验证证书路径
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.entity-id=远程IDP实体ID2
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.sso-url=https://remoteidp2.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.response-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST
spring:
security:
saml2:
relyingparty:
registration:
my-relying-party1:
signing:
credentials:
- private-key-location: "私钥路径"
certificate-location: "证书路径"
decryption:
credentials:
- private-key-location: "私钥路径"
certificate-location: "证书路径"
singlelogout:
url: "https://myapp/logout/saml2/slo"
response-url: "https://remoteidp2.slo.url"
binding: "POST"
assertingparty:
verification:
credentials:
- certificate-location: "验证证书路径"
entity-id: "远程IDP实体ID1"
sso-url: "https://remoteidp1.sso.url"
my-relying-party2:
signing:
credentials:
- private-key-location: "私钥路径"
certificate-location: "证书路径"
decryption:
credentials:
- private-key-location: "私钥路径"
certificate-location: "证书路径"
assertingparty:
verification:
credentials:
- certificate-location: "其他验证证书路径"
entity-id: "远程IDP实体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的Saml2LogoutRequestFilter
和Saml2LogoutResponseFilter
仅处理与/logout/saml2/slo
匹配的URL。如果要自定义AP启动的注销请求发送到的url
或AP发送注销响应到的response-url
,以使用不同的模式,您需要提供配置来处理该自定义模式。例如,对于Servlet应用程序,您可以添加类似以下内容的自定义SecurityFilterChain
:
@Configuration(proxyBeanMethods = false)
public class MySamlRelyingPartyConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.saml2Login(withDefaults());
http.saml2Logout((saml2) -> saml2.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
.logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")));
return http.build();
}
}
5. Spring Session
Spring Boot为各种数据存储提供了Spring Session的自动配置。在构建Servlet Web应用程序时,可以自动配置以下存储:
-
Redis
-
JDBC
-
Hazelcast
-
MongoDB
Servlet自动配置替代了使用@Enable*HttpSession
的需要。
如果类路径上存在单个Spring Session模块,则Spring Boot会自动使用该存储实现。如果存在多个实现,Spring Boot会按照以下顺序选择特定的实现:
-
Redis
-
JDBC
-
Hazelcast
-
MongoDB
-
如果Redis、JDBC、Hazelcast和MongoDB都不可用,则不配置
SessionRepository
。
构建响应式Web应用程序时,可以自动配置以下存储:
-
Redis
-
MongoDB
响应式自动配置替代了使用@Enable*WebSession
的需要。
与Servlet配置类似,如果存在多个实现,Spring Boot会按照以下顺序选择特定的实现:
-
Redis
-
MongoDB
-
如果Redis和MongoDB都不可用,则不配置
ReactiveSessionRepository
。
每个存储都有特定的附加设置。例如,可以自定义JDBC存储的表名称,如下例所示:
spring.session.jdbc.table-name=SESSIONS
spring:
session:
jdbc:
table-name: "SESSIONS"
要设置会话超时,可以使用spring.session.timeout
属性。如果未在Servlet Web应用程序中设置该属性,则自动配置将退回到server.servlet.session.timeout
的值。
您可以使用@Enable*HttpSession
(Servlet)或@Enable*WebSession
(响应式)来控制Spring Session的配置。这将导致自动配置退后。然后可以使用注解的属性来配置Spring Session,而不是之前描述的配置属性。
6. Spring用于GraphQL
如果您想构建GraphQL应用程序,可以利用Spring Boot对Spring GraphQL的自动配置。Spring GraphQL项目基于GraphQL Java。您至少需要spring-boot-starter-graphql
启动器。由于GraphQL是与传输无关的,您还需要在应用程序中添加一个或多个额外的启动器来通过网络公开您的GraphQL API:
启动器 | 传输 | 实现 |
---|---|---|
|
HTTP |
Spring MVC |
|
WebSocket |
Servlet应用程序的WebSocket |
|
HTTP, WebSocket |
Spring WebFlux |
|
TCP, WebSocket |
Reactor Netty上的Spring WebFlux |
6.1. GraphQL模式
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模式,定义了两种类型和两个查询:
type Query {
greeting(name: String! = "Spring"): String!
project(slug: ID!): Project
}
""" Spring产品组合中的一个项目 """
type Project {
""" 用于URL的唯一字符串ID """
slug: ID!
""" 项目名称 """
name: String!
""" git存储库的URL """
repositoryUrl: String!
""" 当前支持状态 """
status: ProjectStatus!
}
enum ProjectStatus {
""" Spring团队积极支持 """
ACTIVE
""" 社区支持 """
COMMUNITY
""" 原型,尚未正式支持 """
INCUBATING
""" 项目正在退役,处于维护模式 """
ATTIC
""" 终止生命周期 """
EOL
}
默认情况下,字段内省将允许在模式上,因为它对于诸如GraphiQL之类的工具是必需的。如果您不希望公开有关模式的信息,可以通过将spring.graphql.schema.introspection.enabled 设置为false 来禁用内省。 |
6.2. GraphQL RuntimeWiring
GraphQL Java的RuntimeWiring.Builder
可用于注册自定义标量类型、指令、类型解析器、DataFetcher
等。您可以在Spring配置中声明RuntimeWiringConfigurer
bean,以便访问RuntimeWiring.Builder
。Spring Boot会检测到这样的bean,并将它们添加到GraphQlSource构建器中。
通常,应用程序不会直接实现DataFetcher
,而是会创建注解控制器。Spring Boot将自动检测带有注解处理程序方法的@Controller
类,并将其注册为DataFetcher
。以下是我们的问候查询的样本实现,使用@Controller
类:
@Controller
public class GreetingController {
@QueryMapping
public String greeting(@Argument String name) {
return "Hello, " + name + "!";
}
}
@Controller
class GreetingController {
@QueryMapping
fun greeting(@Argument name: String): String {
return "Hello, $name!"
}
}
6.3. Querydsl和QueryByExample存储库支持
Spring Data支持Querydsl和QueryByExample存储库。Spring GraphQL可以将Querydsl和QueryByExample存储库配置为DataFetcher
。
带有@GraphQlRepository
注释并扩展以下之一的Spring Data存储库:
-
QuerydslPredicateExecutor
-
ReactiveQuerydslPredicateExecutor
-
QueryByExampleExecutor
-
ReactiveQueryByExampleExecutor
将被Spring Boot检测到,并被视为匹配顶层查询的DataFetcher
候选者。
6.4. 传输
6.4.1. HTTP和WebSocket
GraphQL的HTTP端点默认为HTTP POST /graphql
。路径可以使用spring.graphql.path
进行自定义。
Spring MVC和Spring WebFlux的HTTP端点由具有@Order 为0 的RouterFunction bean提供。如果定义自己的RouterFunction bean,可能需要添加适当的@Order 注解以确保它们正确排序。 |
GraphQL的WebSocket端点默认关闭。要启用它:
-
对于Servlet应用程序,添加WebSocket starter
spring-boot-starter-websocket
-
对于WebFlux应用程序,不需要额外的依赖
-
对于两者,必须设置
spring.graphql.websocket.path
应用程序属性
Spring GraphQL提供了一个Web拦截模型。这对于从HTTP请求头中检索信息并将其设置到GraphQL上下文中,或者从相同上下文中获取信息并将其写入响应头非常有用。使用Spring Boot,您可以声明一个WebInterceptor
bean,以便将其注册到Web传输中。
Spring MVC和Spring WebFlux支持CORS(跨源资源共享)请求。对于通过不同域从浏览器访问的GraphQL应用程序,CORS是Web配置的关键部分。
Spring Boot支持在spring.graphql.cors.*
命名空间下的许多配置属性;以下是一个简短的配置示例:
spring.graphql.cors.allowed-origins=https://example.org
spring.graphql.cors.allowed-methods=GET,POST
spring.graphql.cors.max-age=1800s
spring:
graphql:
cors:
allowed-origins: "https://example.org"
allowed-methods: GET,POST
max-age: 1800s
6.4.2. RSocket
RSocket也支持作为传输,基于WebSocket或TCP。一旦配置了RSocket服务器,我们可以使用spring.graphql.rsocket.mapping
在特定路由上配置我们的GraphQL处理程序。例如,将该映射配置为"graphql"
意味着我们可以在使用RSocketGraphQlClient
发送请求时使用该路由。
Spring Boot自动配置了一个RSocketGraphQlClient.Builder<?>
bean,您可以在组件中注入它:
@Component
public class RSocketGraphQlClientExample {
private final RSocketGraphQlClient graphQlClient;
public RSocketGraphQlClientExample(RSocketGraphQlClient.Builder<?> builder) {
this.graphQlClient = builder.tcp("example.spring.io", 8181).route("graphql").build();
}
@Component
class RSocketGraphQlClientExample(private val builder: RSocketGraphQlClient.Builder<*>) {
然后发送请求:
Mono<Book> book = this.graphQlClient.document("{ bookById(id: \"book-1\"){ id name pageCount author } }")
.retrieve("bookById")
.toEntity(Book.class);
val book = graphQlClient.document(
"""
{
bookById(id: "book-1"){
id
name
pageCount
author
}
}
"""
)
.retrieve("bookById").toEntity(Book::class.java)
6.5. 异常处理
Spring GraphQL使应用程序能够注册一个或多个Spring DataFetcherExceptionResolver
组件,这些组件按顺序调用。异常必须解析为graphql.GraphQLError
对象列表,参见Spring GraphQL异常处理文档。Spring Boot将自动检测DataFetcherExceptionResolver
bean并将它们注册到GraphQlSource.Builder
中。
6.6. GraphiQL和模式打印机
Spring GraphQL提供了基础设施,帮助开发人员在消费或开发GraphQL API时更加便捷。
Spring GraphQL附带一个默认的GraphiQL页面,默认情况下在"/graphiql"
处公开。默认情况下此页面处于禁用状态,可以使用spring.graphql.graphiql.enabled
属性打开。许多公开此页面的应用程序将更喜欢自定义构建。默认实现在开发过程中非常有用,这就是为什么在开发过程中使用spring-boot-devtools
时会自动公开它。
您还可以选择在spring.graphql.schema.printer.enabled
属性启用时以文本格式在/graphql/schema
处公开GraphQL模式。
7. Spring HATEOAS
如果您正在开发一个使用超媒体的RESTful API,Spring Boot为Spring HATEOAS提供了自动配置,适用于大多数应用程序。自动配置替代了使用@EnableHypermediaSupport
的需要,并注册了一些bean,以便轻松构建基于超媒体的应用程序,包括一个LinkDiscoverers
(用于客户端支持)和一个配置正确将响应转换为所需表示形式的ObjectMapper
。通过设置各种spring.jackson.*
属性或(如果存在)通过Jackson2ObjectMapperBuilder
bean来自定义ObjectMapper
。
您可以通过使用@EnableHypermediaSupport
来控制Spring HATEOAS的配置。请注意,这样做会禁用前面描述的ObjectMapper
自定义。
spring-boot-starter-hateoas 专门用于Spring MVC,不应与Spring WebFlux结合使用。为了在Spring WebFlux中使用Spring HATEOAS,您可以直接添加依赖org.springframework.hateoas:spring-hateoas 以及spring-boot-starter-webflux 。 |
默认情况下,接受application/json
的请求将收到一个application/hal+json
的响应。要禁用此行为,请将spring.hateoas.use-hal-as-default-json-media-type
设置为false
,并定义一个HypermediaMappingInformation
或HalConfiguration
来配置Spring HATEOAS以满足您的应用程序及其客户端的需求。