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 框架”
Spring Web MVC 框架(通常称为“Spring MVC”)是一个丰富的“模型视图控制器”Web 框架。Spring MVC 允许您创建特殊的Bean@Controller
或@RestController
Bean 来处理传入的 HTTP 请求。控制器中的方法通过使用注释映射到 HTTP @RequestMapping
。
以下代码显示了@RestController
提供 JSON 数据的典型代码:
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public User getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId).get();
}
@GetMapping("/{userId}/customers")
public List<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
}
@DeleteMapping("/{userId}")
public void deleteUser(@PathVariable Long userId) {
this.userRepository.deleteById(userId);
}
}
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): User {
return userRepository.findById(userId).get()
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): List<Customer> {
return userRepository.findById(userId).map(customerRepository::findByUser).get()
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long) {
userRepository.deleteById(userId)
}
}
“WebMvc.fn”,功能变体,将路由配置与请求的实际处理分开,如以下示例所示:
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun routerFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build()
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
@Component
public class MyUserHandler {
public ServerResponse getUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse getUserCustomers(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse deleteUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
}
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
fun getUserCustomers(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
fun deleteUser(request: ServerRequest?): ServerResponse {
return ServerResponse.ok().build()
}
}
Spring MVC 是核心 Spring 框架的一部分,详细信息可在 参考文档中找到。spring.io/guides上还提供了一些涵盖 Spring MVC 的指南。
您可以定义任意数量的RouterFunction 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 自定义,则可以声明 type 的 beanWebMvcRegistrations
并使用它来提供这些组件的自定义实例。
如果您不想使用自动配置并希望完全控制 Spring MVC,请添加您自己的@Configuration
注释@EnableWebMvc
。或者,添加您自己的@Configuration
-annotated DelegatingWebMvcConfiguration
,如 的 Javadoc 中所述@EnableWebMvc
。
1.1.2. Spring MVC 转换服务
Spring MVC 使用ConversionService
一种与用于转换您application.properties
或application.yaml
文件中的值的方法不同的方法。这意味着Period
,Duration
和DataSize
转换器不可用,并且@DurationUnit
和@DataSizeUnit
注释将被忽略。
如果你想定制ConversionService
Spring MVC使用的,你可以提供一个WebMvcConfigurer
带有方法的bean addFormatters
。通过此方法,您可以注册任何您喜欢的转换器,也可以委托给ApplicationConversionService
.
还可以使用spring.mvc.format.*
配置属性来自定义转换。如果未配置,则使用以下默认值:
财产 | DateTimeFormatter |
---|---|
|
|
|
|
|
|
1.1.3. Http消息转换器
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. 消息代码解析器
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
(or /public
or /resources
or )的目录或. 它使用Spring MVC 中的,以便您可以通过添加自己的方法并覆盖该方法来修改该行为。/META-INF/resources
ServletContext
ResourceHttpRequestHandler
WebMvcConfigurer
addResourceHandlers
在独立的 Web 应用程序中,容器中的默认 servlet 未启用。可以使用该属性来启用它server.servlet.register-default-servlet
。
默认 servlet 充当后备,ServletContext
如果 Spring 决定不处理它,则从根目录提供内容。大多数时候,这种情况不会发生(除非您修改默认的 MVC 配置),因为 Spring 始终可以通过DispatcherServlet
.
默认情况下,资源映射到/**
,但您可以使用 属性对其进行调整spring.mvc.static-path-pattern
。例如,将所有资源重新定位/resources/**
可以实现如下:
spring.mvc.static-path-pattern=/resources/**
spring:
mvc:
static-path-pattern: "/resources/**"
您还可以使用 属性自定义静态资源位置spring.web.resources.static-locations
(用目录位置列表替换默认值)。根 Servlet 上下文路径"/"
也会自动添加为位置。
除了前面提到的“标准”静态资源位置之外,还为Webjars 内容制作了一个特例。/webjars/**
默认情况下,如果以 Webjars 格式打包,则任何路径为 的资源都将从 jar 文件提供。可以使用属性自定义路径spring.mvc.webjars-path-pattern
。
src/main/webapp 如果您的应用程序打包为 jar,
请勿使用该目录。尽管此目录是通用标准,但它仅适用于 war 打包,并且如果您生成 jar,大多数构建工具都会默默地忽略它。
|
Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许使用缓存清除静态资源或对 Webjar 使用版本无关的 URL 等用例。
要对 Webjar 使用与版本无关的 URL,请添加webjars-locator-core
依赖项。然后声明你的Webjar。以jQuery为例,在where中添加"/webjars/jquery/jquery.min.js"
结果是Webjar版本。"/webjars/jquery/x.y.z/jquery.min.js"
x.y.z
如果您使用 JBoss,则需要声明webjars-locator-jboss-vfs 依赖项而不是webjars-locator-core . 否则,所有 Webjar 都会解析为404 .
|
<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
要使用缓存清除,以下配置为所有静态资源配置缓存清除解决方案,有效地在 URL 中添加内容哈希,例如:
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
ResourceUrlEncodingFilter 由于Thymeleaf 和 FreeMarker 自动配置
,资源链接在运行时在模板中重写。使用 JSP 时您应该手动声明此过滤器。目前不自动支持其他模板引擎,但可以使用自定义模板宏/帮助程序以及使用ResourceUrlProvider .
|
例如,使用 JavaScript 模块加载器动态加载资源时,不能选择重命名文件。这就是为什么其他策略也受到支持并且可以组合的原因。“固定”策略在 URL 中添加静态版本字符串而不更改文件名,如下例所示:
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/js/lib/
spring.web.resources.chain.strategy.fixed.version=v12
spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
fixed:
enabled: true
paths: "/js/lib/"
version: "v12"
通过此配置,位于 下的 JavaScript 模块"/js/lib/"
使用固定版本控制策略 ( "/v12/js/lib/mymodule.js"
),而其他资源仍然使用内容一 ( <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
)。
请参阅WebProperties.Resources
参考资料 了解更多支持的选项。
1.1.6。欢迎页面
Spring Boot 支持静态和模板化欢迎页面。index.html
它首先在配置的静态内容位置查找文件。如果没有找到,它就会寻找index
模板。如果找到其中一个,它将自动用作应用程序的欢迎页面。
1.1.8。路径匹配和内容协商
Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射(例如,@GetMapping
控制器方法上的注释)进行匹配,将传入的 HTTP 请求映射到处理程序。
Spring Boot 默认选择禁用后缀模式匹配,这意味着类似的请求"GET /projects/spring-boot.json"
将不会与@GetMapping("/projects/spring-boot")
映射匹配。这被认为是Spring MVC 应用程序的最佳实践。过去,此功能主要适用于未发送正确“Accept”请求标头的 HTTP 客户端;我们需要确保向客户端发送正确的内容类型。如今,内容协商更加可靠。
还有其他方法可以处理不始终发送正确的“Accept”请求标头的 HTTP 客户端。我们可以使用查询参数来确保将请求"GET /projects/spring-boot?format=json"
映射到 ,而不是使用后缀匹配@GetMapping("/projects/spring-boot")
:
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
。请注意,默认情况下,静态内容的服务会映射到/**
并因此为所有请求提供处理程序。对于要抛出的 a NoHandlerFoundException
,您还必须设置spring.mvc.static-path-pattern
为更具体的值,例如/resources/**
或 设置spring.web.resources.add-mappings
为false
完全禁用静态内容的服务。
1.1.9。可配置的WebBinding初始化器
Spring MVC 使用 aWebBindingInitializer
来初始化WebDataBinder
特定请求的 a。如果您创建自己的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 中从其 main 方法运行应用程序会导致与使用 Maven 或 Gradle 或从其打包的 jar 运行应用程序时不同的顺序。这可能会导致 Spring Boot 无法找到预期的模板。如果遇到此问题,可以在 IDE 中重新排序类路径,将模块的类和资源放在前面。 |
1.1.11. 错误处理
默认情况下,Spring Boot 提供了一个/error
以合理方式处理所有错误的映射,并且它在 servlet 容器中注册为“全局”错误页面。对于机器客户端,它会生成一个 JSON 响应,其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户端,有一个“whitelabel”错误视图,它以 HTML 格式呈现相同的数据(要自定义它,请添加解析View
为 的error
)。
server.error
如果您想自定义默认的错误处理行为,可以设置许多属性。请参阅附录的“服务器属性”部分。
要完全替换默认行为,您可以实现ErrorController
并注册该类型的 bean 定义,或者添加该类型的 beanErrorAttributes
以使用现有机制但替换内容。
可以BasicErrorController 用作自定义的基类ErrorController 。如果您想为新的内容类型添加处理程序(默认情况是专门处理text/html 并为其他所有内容提供后备),这特别有用。为此,请扩展BasicErrorController ,添加一个@RequestMapping 具有produces 属性的公共方法,然后创建一个新类型的 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
,则使用 POJO 的 JSON 表示形式MyErrorBody
而不是该ErrorAttributes
表示形式。
在某些情况下,度量基础设施不会记录在控制器级别处理的错误。应用程序可以通过将已处理的异常设置为请求属性来确保此类异常与请求指标一起记录:
@Controller
public class MyController {
@ExceptionHandler(CustomException.class)
String handleCustomException(HttpServletRequest request, CustomException ex) {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
return "errorView";
}
}
@Controller
class MyController {
@ExceptionHandler(CustomException::class)
fun handleCustomException(request: HttpServletRequest, ex: CustomException?): String {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex)
return "errorView"
}
}
自定义错误页面
如果要显示给定状态代码的自定义 HTML 错误页面,可以将文件添加到目录/error
。错误页面可以是静态 HTML(即添加在任何静态资源目录下),也可以使用模板构建。文件名应该是确切的状态代码或系列掩码。
例如,要映射404
到静态 HTML 文件,您的目录结构将如下所示:
src/ +- main/ +- java/ | + <source code> +- resources/ +- public/ +- error/ | +- 404.html +- <other public assets>
要使用 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) {
// Use the request or status to optionally return a ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
new ModelAndView("myview");
}
return null;
}
}
class MyErrorViewResolver : ErrorViewResolver {
override fun resolveErrorView(request: HttpServletRequest, status: HttpStatus,
model: Map<String, Any>): ModelAndView? {
// Use the request or status to optionally return a ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
return ModelAndView("myview")
}
return null
}
}
您还可以使用常规 Spring MVC 功能,例如@ExceptionHandler
方法和@ControllerAdvice
. thenErrorController
会拾取任何未处理的异常。
在 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"))
}
}
如果您注册 an 的ErrorPage 路径最终由 a 处理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 的服务方法后提交响应。com.ibm.ws.webcontainer.invokeFlushAfterService
您应该通过设置为 来禁用此行为false
。
1.1.12. CORS 支持
从版本 4.2 开始,Spring MVC支持 CORS。在 Spring Boot 应用程序中使用带注释的控制器方法 CORS 配置@CrossOrigin
不需要任何特定配置。
可以通过自定义方法注册bean来定义全局CORS配置,如下例所示:WebMvcConfigurer
addCorsMappings(CorsRegistry)
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
}
}
}
}
1.2. JAX-RS 和泽西岛
如果您更喜欢 REST 端点的 JAX-RS 编程模型,则可以使用可用的实现之一而不是 Spring MVC。
Jersey和Apache CXF开箱即用,运行良好。CXF 要求您在应用程序上下文中将其注册为Servlet
或Filter
作为 a 。@Bean
Jersey 有一些原生 Spring 支持,因此我们还在 Spring Boot 中为其提供自动配置支持以及启动器。
要开始使用 Jersey,请将 包含spring-boot-starter-jersey
为依赖项,然后您需要注册所有端点的@Bean
类型之一ResourceConfig
,如以下示例所示:
@Component
public class MyJerseyConfig extends ResourceConfig {
public MyJerseyConfig() {
register(MyEndpoint.class);
}
}
Jersey 对扫描可执行档案的支持相当有限。例如,它无法扫描在完全可执行的 jar 文件中或WEB-INF/classes 运行可执行的 war 文件时找到的包中的端点。为了避免这种限制,packages 不应使用该方法,而应使用该register 方法单独注册端点,如前面的示例所示。
|
对于更高级的自定义,您还可以注册任意数量的实现ResourceConfigCustomizer
.
所有注册的端点都应该带有@Components
HTTP资源注释(@GET
和其他),如下例所示:
@Component
@Path("/hello")
public class MyEndpoint {
@GET
public String message() {
return "Hello";
}
}
由于它Endpoint
是一个 Spring @Component
,它的生命周期由 Spring 管理,您可以使用@Autowired
注解注入依赖项,并使用@Value
注解注入外部配置。默认情况下,Jersey servlet 已注册并映射到/*
. 您可以通过添加@ApplicationPath
到您的ResourceConfig
.
默认情况下,Jersey 设置为名为类型的@Bean
servlet 。默认情况下,Servlet 是延迟初始化的,但您可以通过设置 来自定义该行为。您可以通过创建您自己的同名 bean 来禁用或覆盖该 bean。您还可以通过设置使用过滤器而不是 servlet (在这种情况下,要替换或覆盖的是)。该过滤器有一个,您可以使用 进行设置。当使用 Jersey 作为过滤器时,必须存在一个 servlet 来处理任何未被 Jersey 拦截的请求。如果您的应用程序不包含此类 servlet,您可能需要通过设置为 来启用默认 servlet 。servlet 和过滤器注册都可以通过使用指定属性映射来获得初始化参数。ServletRegistrationBean
jerseyServletRegistration
spring.jersey.servlet.load-on-startup
spring.jersey.type=filter
@Bean
jerseyFilterRegistration
@Order
spring.jersey.filter.order
server.servlet.register-default-servlet
true
spring.jersey.init.*
1.3. 嵌入式 Servlet 容器支持
对于 servlet 应用程序,Spring Boot 包括对嵌入式Tomcat、Jetty和Undertow服务器的支持。大多数开发人员使用适当的“Starter”来获取完全配置的实例。默认情况下,嵌入式服务器侦听端口 上的 HTTP 请求8080
。
1.3.1. Servlet、过滤器和侦听器
HttpSessionListener
使用嵌入式 servlet 容器时,您可以通过使用 Spring beans 或扫描 servlet 组件来注册 servlet、过滤器和 servlet 规范中的所有侦听器(例如)。
将 Servlet、过滤器和侦听器注册为 Spring Bean
任何属于Servlet
Spring bean 的Filter
、 或 servlet*Listener
实例都注册到嵌入式容器中。如果您想在配置期间引用一个值,这会特别方便application.properties
。
默认情况下,如果上下文仅包含单个 Servlet,则它将映射到/
. 对于多个 servlet bean,bean 名称用作路径前缀。过滤器映射到/*
.
如果基于约定的映射不够灵活,您可以使用ServletRegistrationBean
、FilterRegistrationBean
和ServletListenerRegistrationBean
类进行完全控制。
通常,让滤豆保持无序状态是安全的。如果需要特定的顺序,您应该注释Filter
或@Order
使其实现Ordered
。Filter
您不能通过使用 注释其 bean 方法来配置 a 的顺序@Order
。如果您无法更改Filter
要添加@Order
或实现的类Ordered
,则必须定义FilterRegistrationBean
并Filter
使用 方法设置注册 bean 的顺序setOrder(int)
。避免配置读取请求正文的过滤器Ordered.HIGHEST_PRECEDENCE
,因为它可能会违反应用程序的字符编码配置。如果 servlet 过滤器包装请求,则应使用小于或等于 的顺序对其进行配置OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER
。
Filter 要查看应用程序中
每个项的顺序,请为web 日志记录组( logging.level.web=debug ) 启用调试级别日志记录。已注册过滤器的详细信息(包括它们的顺序和 URL 模式)将在启动时记录下来。
|
注册 bean 时要小心,Filter 因为它们是在应用程序生命周期的早期初始化的。如果您需要注册Filter 与其他 bean 交互的 a,请考虑使用 aDelegatingFilterProxyRegistrationBean 来代替。
|
1.3.2. Servlet 上下文初始化
嵌入式servlet容器不直接执行jakarta.servlet.ServletContainerInitializer
接口或Spring的org.springframework.web.WebApplicationInitializer
接口。这是一个有意的设计决策,旨在降低设计用于在战争中运行的第三方库可能破坏 Spring Boot 应用程序的风险。
如果需要在 Spring Boot 应用程序中执行 servlet 上下文初始化,则应该注册一个实现该org.springframework.boot.web.servlet.ServletContextInitializer
接口的 bean。单一onStartup
方法提供对 的访问ServletContext
,并且如果需要,可以轻松地用作现有WebApplicationInitializer
.
1.3.3. ServletWebServerApplicationContext
在底层,Spring Boot 使用不同类型的ApplicationContext
嵌入式 servlet 容器支持。这ServletWebServerApplicationContext
是一种特殊类型WebApplicationContext
,它通过搜索单个ServletWebServerFactory
bean 来引导自身。通常TomcatServletWebServerFactory
、JettyServletWebServerFactory
、 或UndertowServletWebServerFactory
已自动配置。
您通常不需要了解这些实现类。大多数应用程序都是自动配置的,并且会代表您创建适当的应用ApplicationContext 程序ServletWebServerFactory 。
|
在嵌入式容器设置中,它ServletContext
被设置为服务器启动的一部分,该启动在应用程序上下文初始化期间发生。因此,ApplicationContext
不能使用 可靠地初始化中的 beans 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 容器
常见的 servlet 容器设置可以使用 SpringEnvironment
属性进行配置。application.properties
通常,您可以在您的或文件中定义属性application.yaml
。
常见的服务器设置包括:
Spring Boot 尝试尽可能多地公开通用设置,但这并不总是可行。对于这些情况,专用命名空间提供特定于服务器的自定义(请参阅server.tomcat
和server.undertow
)。例如,可以使用嵌入式 servlet 容器的特定功能来配置访问日志。
请参阅ServerProperties 课程以获取完整列表。
|
同站点 Cookie
Web 浏览器可以使用cookieSameSite
属性来控制在跨站点请求中是否提交 cookie 以及如何提交。该属性与现代 Web 浏览器特别相关,现代 Web 浏览器已开始更改该属性丢失时使用的默认值。
如果您想更改SameSite
会话 cookie 的属性,可以使用该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"
如果您想更改SameSite
添加到您的其他 cookie 上的属性HttpServletResponse
,您可以使用CookieSameSiteSupplier
. CookieSameSiteSupplier
传递 a并Cookie
可能返回一个SameSite
值,或null
。
您可以使用许多便利的工厂和过滤器方法来快速匹配特定的 cookie。例如,添加以下 bean 将自动为名称与正则表达式匹配的所有 cookie应用SameSite
a 。Lax
myapp.*
@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
@Bean
public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
}
}
@Configuration(proxyBeanMethods = false)
class MySameSiteConfiguration {
@Bean
fun applicationCookieSameSiteSupplier(): CookieSameSiteSupplier {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*")
}
}
程序化定制
如果您需要以编程方式配置嵌入式 servlet 容器,您可以注册一个实现该WebServerFactoryCustomizer
接口的 Spring bean。
WebServerFactoryCustomizer
提供对 的访问ConfigurableServletWebServerFactory
,其中包括许多自定义设置器方法。以下示例显示以编程方式设置端口:
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
override fun customize(server: ConfigurableServletWebServerFactory) {
server.setPort(9000)
}
}
TomcatServletWebServerFactory
、JettyServletWebServerFactory
和UndertowServletWebServerFactory
是其专用变体,ConfigurableServletWebServerFactory
分别为 Tomcat、Jetty 和 Undertow 提供额外的自定义 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。
为许多配置选项提供了设置器。如果您需要做一些更奇特的事情,还提供了几个受保护的方法“挂钩”。详细信息请参见源代码文档。
自动配置的定制器仍然应用于您的定制工厂,因此请谨慎使用该选项。 |
2. 响应式 Web 应用程序
Spring Boot 通过为 Spring Webflux 提供自动配置来简化响应式 Web 应用程序的开发。
2.1. “Spring WebFlux 框架”
Spring WebFlux 是 Spring Framework 5.0 中引入的新的响应式 Web 框架。与 Spring MVC 不同,它不需要 servlet API,完全异步且非阻塞,并通过Reactor 项目实现Reactive Streams规范。
Spring WebFlux 有两种风格:函数式和基于注释的。基于注解的模型非常接近 Spring MVC 模型,如下例所示:
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public Mono<User> getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId);
}
@GetMapping("/{userId}/customers")
public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
}
@DeleteMapping("/{userId}")
public Mono<Void> deleteUser(@PathVariable Long userId) {
return this.userRepository.deleteById(userId);
}
}
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): Mono<User?> {
return userRepository.findById(userId)
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): Flux<Customer> {
return userRepository.findById(userId).flatMapMany { user: User? ->
customerRepository.findByUser(user)
}
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long): Mono<Void> {
return userRepository.deleteById(userId)
}
}
“WebFlux.fn”,功能变体,将路由配置与请求的实际处理分开,如以下示例所示:
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun monoRouterFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route(
GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute(
GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute(
DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser)
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
@Component
public class MyUserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
...
}
}
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
return ServerResponse.ok().build()
}
}
WebFlux 是 Spring 框架的一部分,详细信息可在其参考文档中找到。
您可以定义任意数量的RouterFunction 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
type 类WebFluxConfigurer
,但不添加 @EnableWebFlux
.
如果你想完全控制Spring WebFlux,你可以添加你自己的@Configuration
注释@EnableWebFlux
。
2.1.2. Spring WebFlux 转换服务
如果你想自定义ConversionService
Spring WebFlux使用的,你可以提供一个WebFluxConfigurer
带有方法的bean addFormatters
。
还可以使用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
(or /public
or /resources
or )的目录提供静态内容。/META-INF/resources
它使用ResourceWebHandler
Spring WebFlux,以便您可以通过添加自己的方法WebFluxConfigurer
并覆盖该方法来修改该行为addResourceHandlers
。
默认情况下,资源映射到/**
,但您可以通过设置属性来调整它spring.webflux.static-path-pattern
。例如,将所有资源重新定位/resources/**
可以实现如下:
spring.webflux.static-path-pattern=/resources/**
spring:
webflux:
static-path-pattern: "/resources/**"
您还可以使用 自定义静态资源位置spring.web.resources.static-locations
。这样做会用目录位置列表替换默认值。如果您这样做,默认欢迎页面检测将切换到您的自定义位置。因此,如果启动时在任何位置有index.html
,则它是应用程序的主页。
除了前面列出的“标准”静态资源位置之外,还为Webjars 内容制作了一个特殊情况。/webjars/**
默认情况下,如果以 Webjars 格式打包,则任何路径为 的资源都将从 jar 文件提供。可以使用属性自定义路径spring.webflux.webjars-path-pattern
。
Spring WebFlux应用程序并不严格依赖于servlet API,因此它们不能部署为war文件并且不使用该src/main/webapp 目录。
|
2.1.5。欢迎页面
Spring Boot 支持静态和模板化欢迎页面。index.html
它首先在配置的静态内容位置查找文件。如果没有找到,它就会寻找index
模板。如果找到其中一个,它将自动用作应用程序的欢迎页面。
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 提供的处理程序之前,这些处理程序被认为是最后的。对于机器客户端,它会生成一个 JSON 响应,其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户端,有一个“whitelabel”错误处理程序,可以以 HTML 格式呈现相同的数据。您还可以提供自己的 HTML 模板来显示错误(请参阅下一节)。
在直接在 Spring Boot 中自定义错误处理之前,您可以利用Spring WebFlux 中的RFC 7807 Problem Details支持。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
。
定制此功能的第一步通常涉及使用现有机制,但替换或增加错误内容。为此,您可以添加类型为 的 bean ErrorAttributes
。
要更改错误处理行为,您可以实现ErrorWebExceptionHandler
并注册该类型的 bean 定义。由于 anErrorWebExceptionHandler
是相当低级的,Spring Boot 还提供了一种方便的方式AbstractErrorWebExceptionHandler
让您以 WebFlux 功能方式处理错误,如以下示例所示:
@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources,
ApplicationContext applicationContext) {
super(errorAttributes, resources, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
}
private boolean acceptsXml(ServerRequest request) {
return request.headers().accept().contains(MediaType.APPLICATION_XML);
}
public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
// ... additional builder calls
return builder.build();
}
}
@Component
class MyErrorWebExceptionHandler(errorAttributes: ErrorAttributes?, resources: WebProperties.Resources?,
applicationContext: ApplicationContext?) : AbstractErrorWebExceptionHandler(errorAttributes, resources, applicationContext) {
override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
}
private fun acceptsXml(request: ServerRequest): Boolean {
return request.headers().accept().contains(MediaType.APPLICATION_XML)
}
fun handleErrorAsXml(request: ServerRequest?): Mono<ServerResponse> {
val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
// ... additional builder calls
return builder.build()
}
}
为了获得更完整的图片,您还可以DefaultErrorWebExceptionHandler
直接子类化并重写特定方法。
在某些情况下,度量基础设施不会记录在控制器或处理函数级别处理的错误。应用程序可以通过将已处理的异常设置为请求属性来确保此类异常与请求指标一起记录:
@Controller
public class MyExceptionHandlingController {
@GetMapping("/profile")
public Rendering userProfile() {
// ...
throw new IllegalStateException();
}
@ExceptionHandler(IllegalStateException.class)
public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) {
exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build();
}
}
@Controller
class MyExceptionHandlingController {
@GetMapping("/profile")
fun userProfile(): Rendering {
// ...
throw IllegalStateException()
}
@ExceptionHandler(IllegalStateException::class)
fun handleIllegalState(exchange: ServerWebExchange, exc: IllegalStateException): Rendering {
exchange.attributes.putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc)
return Rendering.view("errorView").modelAttribute("message", exc.message ?: "").build()
}
}
自定义错误页面
如果要显示给定状态代码的自定义 HTML 错误页面,您可以添加从 解析的视图error/*
,例如通过将文件添加到/error
目录。错误页面可以是静态 HTML(即添加在任何静态资源目录下)或使用模板构建。文件的名称应该是准确的状态代码、状态代码系列掩码,或者error
如果没有其他匹配的情况则为默认值。请注意,默认错误视图的路径是error/error
,而对于 Spring MVC,默认错误视图的路径是error
。
例如,要映射404
到静态 HTML 文件,您的目录结构将如下所示:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
要使用 Mustache 模板映射所有5xx
错误,您的目录结构将如下所示:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates>
2.1.8. 网页过滤器
Spring WebFlux 提供了一个WebFilter
可以实现过滤 HTTP 请求-响应交换的接口。
WebFilter
在应用程序上下文中找到的 bean 将自动用于过滤每个交换。
如果过滤器的顺序很重要,则可以使用 来实现Ordered
或注释@Order
。Spring Boot 自动配置可以为您配置 Web 过滤器。执行此操作时,将使用下表中显示的顺序:
网页过滤器 | 命令 |
---|---|
|
|
|
|
|
|
2.2. 嵌入式反应式服务器支持
Spring Boot 支持以下嵌入式反应式 Web 服务器:Reactor Netty、Tomcat、Jetty 和 Undertow。大多数开发人员使用适当的“Starter”来获取完全配置的实例。默认情况下,嵌入式服务器在端口 8080 上侦听 HTTP 请求。
2.3. 反应式服务器资源配置
自动配置 Reactor Netty 或 Jetty 服务器时,Spring Boot 将创建特定的 bean,为服务器实例提供 HTTP 资源:ReactorResourceFactory
或JettyResourceFactory
。
默认情况下,这些资源也将与 Reactor Netty 和 Jetty 客户端共享,以获得最佳性能,前提是:
-
服务器和客户端使用相同的技术
-
客户端实例是使用
WebClient.Builder
Spring Boot 自动配置的 bean构建的
ReactorResourceFactory
开发人员可以通过提供自定义或bean来覆盖 Jetty 和 Reactor Netty 的资源配置JettyResourceFactory
- 这将应用于客户端和服务器。
您可以在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 的文档。
|
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 This generated password is for development use only. Your security configuration must be updated before running your application in production.
如果您微调日志记录配置,请确保org.springframework.boot.autoconfigure.security 类别设置为日志WARN 级别消息。否则,不会打印默认密码。
|
您可以通过提供spring.security.user.name
和来更改用户名和密码spring.security.user.password
。
Web 应用程序默认提供的基本功能包括:
-
具有内存存储的
UserDetailsService
(或者在 WebFlux 应用程序的情况下)bean 和具有生成密码的单个用户(请参阅 参考资料 中的用户属性)。ReactiveUserDetailsService
SecurityProperties.User
-
Accept
整个应用程序(包括执行器端点,如果执行器位于类路径上)的基于表单的登录或 HTTP 基本安全性(取决于请求中的标头)。 -
DefaultAuthenticationEventPublisher
用于发布身份验证事件的A。
您可以AuthenticationEventPublisher
通过为其添加一个 bean 来提供不同的内容。
4.1. MVC安全
SecurityAutoConfiguration
默认安全配置在和中实现UserDetailsServiceAutoConfiguration
。
SecurityAutoConfiguration
导入SpringBootWebSecurityConfiguration
用于 Web 安全并UserDetailsServiceAutoConfiguration
配置身份验证,这在非 Web 应用程序中也相关。要完全关闭默认的 Web 应用程序安全配置或组合多个 Spring Security 组件(例如 OAuth2 客户端和资源服务器),请添加 type 的 bean SecurityFilterChain
(这样做不会禁用UserDetailsService
配置或执行器的安全性)。
要同时关闭配置,您可以添加类型为、或 的UserDetailsService
bean 。UserDetailsService
AuthenticationProvider
AuthenticationManager
可以通过添加自定义 bean 来覆盖访问规则SecurityFilterChain
。Spring Boot 提供了方便的方法,可用于覆盖执行器端点和静态资源的访问规则。
EndpointRequest
可以用来创建一个RequestMatcher
基于management.endpoints.web.base-path
属性的。
PathRequest
可用于RequestMatcher
在常用位置创建资源。
4.2. WebFlux 安全
与 Spring MVC 应用程序类似,您可以通过添加依赖项来保护您的 WebFlux 应用程序spring-boot-starter-security
。ReactiveSecurityAutoConfiguration
默认安全配置在和中实现UserDetailsServiceAutoConfiguration
。
ReactiveSecurityAutoConfiguration
导入WebFluxSecurityConfiguration
用于 Web 安全并UserDetailsServiceAutoConfiguration
配置身份验证,这在非 Web 应用程序中也相关。要完全关闭默认的 Web 应用程序安全配置,您可以添加 type 的 bean WebFilterChainProxy
(这样做不会禁用UserDetailsService
配置或 Actuator 的安全性)。
要关闭配置,您可以添加类型为或 的UserDetailsService
bean 。ReactiveUserDetailsService
ReactiveAuthenticationManager
访问规则和多个 Spring Security 组件(例如 OAuth 2 客户端和资源服务器)的使用可以通过添加自定义SecurityWebFilterChain
bean 来配置。Spring Boot 提供了方便的方法,可用于覆盖执行器端点和静态资源的访问规则。
EndpointRequest
可以用来创建一个ServerWebExchangeMatcher
基于management.endpoints.web.base-path
属性的。
PathRequest
可用于ServerWebExchangeMatcher
在常用位置创建资源。
例如,您可以通过添加以下内容来自定义安全配置:
@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange((exchange) -> {
exchange.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
exchange.pathMatchers("/foo", "/bar").authenticated();
});
http.formLogin(withDefaults());
return http.build();
}
}
@Configuration(proxyBeanMethods = false)
class MyWebFluxSecurityConfiguration {
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
http.authorizeExchange { spec ->
spec.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
spec.pathMatchers("/foo", "/bar").authenticated()
}
http.formLogin(withDefaults())
return http.build()
}
}
4.3. OAuth2
OAuth2是 Spring 支持的广泛使用的授权框架。
4.3.1. 客户
如果您spring-security-oauth2-client
的类路径上有,您可以利用一些自动配置来设置 OAuth2/Open ID Connect 客户端。此配置利用 下的属性OAuth2ClientProperties
。相同的属性适用于 servlet 和反应式应用程序。
您可以在该前缀下注册多个 OAuth2 客户端和提供程序spring.security.oauth2.client
,如下例所示:
spring.security.oauth2.client.registration.my-login-client.client-id=abcd
spring.security.oauth2.client.registration.my-login-client.client-secret=password
spring.security.oauth2.client.registration.my-login-client.client-name=Client for OpenID Connect
spring.security.oauth2.client.registration.my-login-client.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-login-client.scope=openid,profile,email,phone,address
spring.security.oauth2.client.registration.my-login-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.my-login-client.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-login-client.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri={baseUrl}/authorized/user
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri={baseUrl}/authorized/email
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server.com/oauth2/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server.com/oauth2/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server.com/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server.com/oauth2/jwks
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
spring:
security:
oauth2:
client:
registration:
my-login-client:
client-id: "abcd"
client-secret: "password"
client-name: "Client for OpenID Connect"
provider: "my-oauth-provider"
scope: "openid,profile,email,phone,address"
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
client-authentication-method: "client_secret_basic"
authorization-grant-type: "authorization_code"
my-client-1:
client-id: "abcd"
client-secret: "password"
client-name: "Client for user scope"
provider: "my-oauth-provider"
scope: "user"
redirect-uri: "{baseUrl}/authorized/user"
client-authentication-method: "client_secret_basic"
authorization-grant-type: "authorization_code"
my-client-2:
client-id: "abcd"
client-secret: "password"
client-name: "Client for email scope"
provider: "my-oauth-provider"
scope: "email"
redirect-uri: "{baseUrl}/authorized/email"
client-authentication-method: "client_secret_basic"
authorization-grant-type: "authorization_code"
provider:
my-oauth-provider:
authorization-uri: "https://my-auth-server.com/oauth2/authorize"
token-uri: "https://my-auth-server.com/oauth2/token"
user-info-uri: "https://my-auth-server.com/userinfo"
user-info-authentication-method: "header"
jwk-set-uri: "https://my-auth-server.com/oauth2/jwks"
user-name-attribute: "name"
对于支持 OpenID Connect discovery 的OpenID Connect 提供商,可以进一步简化配置。提供者需要配置一个issuer-uri
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 SecurityOAuth2LoginAuthenticationFilter
仅处理匹配/login/oauth2/code/*
. 如果您想自定义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 包含值为 的 aud 声明my-audience
:
spring.security.oauth2.resourceserver.jwt.audiences[0]=my-audience
spring:
security:
oauth2:
resourceserver:
jwt:
audiences:
- "my-audience"
相同的属性适用于 servlet 和反应式应用程序。JwtDecoder
或者,您可以为 servlet 应用程序或ReactiveJwtDecoder
反应式应用程序定义自己的bean。
如果使用不透明令牌而不是 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 和反应式应用程序。OpaqueTokenIntrospector
或者,您可以为 servlet 应用程序或ReactiveOpaqueTokenIntrospector
反应式应用程序定义自己的bean。
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"
该client-secret 属性的格式必须能够与配置的PasswordEncoder . 的默认实例PasswordEncoder 是通过创建的PasswordEncoderFactories.createDelegatingPasswordEncoder() 。
|
Spring Boot 为 Spring Authorization Server 提供的自动配置旨在快速入门。大多数应用程序都需要定制,并且需要定义多个 bean 来覆盖自动配置。
以下组件可以定义为 bean 来覆盖特定于 Spring Authorization Server 的自动配置:
-
RegisteredClientRepository
-
AuthorizationServerSettings
-
SecurityFilterChain
-
com.nimbusds.jose.jwk.source.JWKSource<com.nimbusds.jose.proc.SecurityContext>
-
JwtDecoder
Spring Boot 自动配置InMemoryRegisteredClientRepository Spring 授权服务器用于管理注册客户端的权限。其InMemoryRegisteredClientRepository 功能有限,我们建议仅将其用于开发环境。对于生产环境,请考虑使用JdbcRegisteredClientRepository 或创建您自己的RegisteredClientRepository .
|
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=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.response-url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.binding=POST
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.verification.credentials[0].certificate-location=path-to-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.entity-id=remote-idp-entity-id1
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.sso-url=https://remoteidp1.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.verification.credentials[0].certificate-location=path-to-other-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.entity-id=remote-idp-entity-id2
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.sso-url=https://remoteidp2.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.response-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST
spring:
security:
saml2:
relyingparty:
registration:
my-relying-party1:
signing:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
decryption:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
singlelogout:
url: "https://myapp/logout/saml2/slo"
response-url: "https://remoteidp2.slo.url"
binding: "POST"
assertingparty:
verification:
credentials:
- certificate-location: "path-to-verification-cert"
entity-id: "remote-idp-entity-id1"
sso-url: "https://remoteidp1.sso.url"
my-relying-party2:
signing:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
decryption:
credentials:
- private-key-location: "path-to-private-key"
certificate-location: "path-to-certificate"
assertingparty:
verification:
credentials:
- certificate-location: "path-to-other-verification-cert"
entity-id: "remote-idp-entity-id2"
sso-url: "https://remoteidp2.sso.url"
singlelogout:
url: "https://remoteidp2.slo.url"
response-url: "https://myapp/logout/saml2/slo"
binding: "POST"
对于 SAML2 注销,默认情况下,Spring SecuritySaml2LogoutRequestFilter
只Saml2LogoutResponseFilter
处理匹配的 URL /logout/saml2/slo
。如果您想要自定义url
AP 发起的注销请求发送到的目标或response-url
AP 发送注销响应的目标,以使用不同的模式,您需要提供配置来处理该自定义模式。例如,对于 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 Boot为各种数据存储提供Spring Session自动配置。构建 servlet Web 应用程序时,可以自动配置以下存储:
-
雷迪斯
-
数据库连接
-
榛卡斯特
-
MongoDB
Servlet 自动配置取代了使用@Enable*HttpSession
.
如果类路径上存在单个 Spring Session 模块,Spring Boot 会自动使用该存储实现。如果您有多个实现,Spring Boot 将使用以下顺序来选择特定实现:
-
雷迪斯
-
数据库连接
-
榛卡斯特
-
MongoDB
-
如果 Redis、JDBC、Hazelcast 和 MongoDB 都不可用,我们不会配置
SessionRepository
.
构建响应式 Web 应用程序时,可以自动配置以下存储:
-
雷迪斯
-
MongoDB
反应式自动配置取代了使用@Enable*WebSession
.
与 servlet 配置类似,如果您有多个实现,Spring Boot 将使用以下顺序来选择特定实现:
-
雷迪斯
-
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
(reactive)来控制 Spring Session 的配置。这将导致自动配置停止。然后可以使用注释的属性而不是前面描述的配置属性来配置 Spring Session。
6. GraphQL 的 Spring
如果您想构建 GraphQL 应用程序,可以利用 Spring Boot 对Spring for GraphQL的自动配置。Spring for GraphQL 项目基于GraphQL Java。spring-boot-starter-graphql
您至少需要启动器。由于 GraphQL 与传输无关,因此您还需要在应用程序中添加一个或多个额外的启动器才能通过网络公开您的 GraphQL API:
起动机 | 运输 | 执行 |
---|---|---|
|
HTTP协议 |
春季MVC |
|
WebSocket |
用于 Servlet 应用程序的 WebSocket |
|
HTTP、网络套接字 |
Spring WebFlux |
|
TCP、网络套接字 |
Reactor Netty 上的 Spring WebFlux |
6.1. GraphQL 架构
Spring GraphQL 应用程序在启动时需要一个定义的模式。默认情况下,您可以在下面写入“.graphqls”或“.gqls”模式文件src/main/resources/graphql/**
,Spring Boot 将自动选择它们。您可以自定义位置spring.graphql.schema.locations
和文件扩展名spring.graphql.schema.file-extensions
。
如果您希望 Spring Boot 检测所有应用程序模块中的架构文件以及该位置的依赖项,您可以设置spring.graphql.schema.locations 为"classpath*:graphql/**/" (注意classpath*: 前缀)。
|
在以下部分中,我们将考虑这个示例 GraphQL 模式,定义两种类型和两个查询:
type Query {
greeting(name: String! = "Spring"): String!
project(slug: ID!): Project
}
""" A Project in the Spring portfolio """
type Project {
""" Unique string id used in URLs """
slug: ID!
""" Project name """
name: String!
""" URL of the git repository """
repositoryUrl: String!
""" Current support status """
status: ProjectStatus!
}
enum ProjectStatus {
""" Actively supported by the Spring team """
ACTIVE
""" Supported by the community """
COMMUNITY
""" Prototype, not officially supported yet """
INCUBATING
""" Project being retired, in maintenance mode """
ATTIC
""" End-Of-Lifed """
EOL
}
默认情况下,模式上将允许进行字段自省,因为这是 GraphiQL 等工具所必需的。如果您不希望公开有关架构的信息,可以通过设置spring.graphql.schema.introspection.enabled 为 来禁用自省false 。
|
6.2. GraphQL 运行时接线
GraphQL JavaRuntimeWiring.Builder
可用于注册自定义标量类型、指令、类型解析器DataFetcher
等。您可以RuntimeWiringConfigurer
在 Spring 配置中声明 beans 以访问RuntimeWiring.Builder
. Spring Boot 检测此类 bean 并将它们添加到GraphQlSource builder中。
然而,通常情况下,应用程序不会DataFetcher
直接实现,而是创建带注释的控制器。Spring Boot 将自动检测@Controller
带有注释处理方法的类并将其注册为DataFetcher
s。以下是我们的问候语查询的类实现示例@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
.
Spring Data 存储库注释@GraphQlRepository
并扩展以下之一:
-
QuerydslPredicateExecutor
-
ReactiveQuerydslPredicateExecutor
-
QueryByExampleExecutor
-
ReactiveQueryByExampleExecutor
由 Spring Boot 检测到并视为DataFetcher
匹配顶级查询的候选者。
6.4. 交通
6.4.1. HTTP 和 WebSocket
默认情况下,GraphQL HTTP 端点位于 HTTP POST /graphql
。可以使用 自定义路径spring.graphql.path
。
Spring MVC 和 Spring WebFlux 的 HTTP 端点都是由RouterFunction 带有@Order of的 bean 提供的0 。如果您定义自己的RouterFunction bean,您可能需要添加适当的@Order 注释以确保它们正确排序。
|
GraphQL WebSocket 端点默认处于关闭状态。要启用它:
-
对于 Servlet 应用程序,添加 WebSocket 启动器
spring-boot-starter-websocket
-
对于 WebFlux 应用程序,不需要额外的依赖项
-
对于两者,都
spring.graphql.websocket.path
必须设置应用程序属性
Spring GraphQL 提供了Web 拦截模型。这对于从 HTTP 请求标头检索信息并将其设置在 GraphQL 上下文中或从同一上下文中获取信息并将其写入响应标头非常有用。使用 Spring Boot,您可以声明一个WebInterceptor
bean 以将其注册到 Web 传输。
Spring MVC和Spring WebFlux支持 CORS(跨源资源共享)请求。CORS 是 GraphQL 应用程序 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 服务器后,我们可以使用在特定路由上配置 GraphQL 处理程序spring.graphql.rsocket.mapping
。例如,将该映射配置为"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. 异常处理
DataFetcherExceptionResolver
Spring GraphQL 使应用程序能够注册一个或多个按顺序调用的Spring组件。异常必须解析为对象列表graphql.GraphQLError
,请参阅Spring GraphQL 异常处理文档。Spring Boot 将自动检测DataFetcherExceptionResolver
bean 并将它们注册到GraphQlSource.Builder
.
6.6. GraphiQL 和 Schema 打印机
Spring GraphQL 提供了基础设施来帮助开发人员使用或开发 GraphQL API。
Spring GraphQL 附带了默认公开的默认GraphiQL页面。"/graphiql"
该页面默认处于禁用状态,可以通过该属性打开spring.graphql.graphiql.enabled
。许多公开此类页面的应用程序更喜欢自定义构建。默认实现在开发过程中非常有用,这就是为什么它spring-boot-devtools
在开发过程中自动公开的原因。
您还可以选择在启用/graphql/schema
该属性时以文本格式公开 GraphQL 架构。spring.graphql.schema.printer.enabled
7. 春天的仇恨
如果您开发使用超媒体的 RESTful API,Spring Boot 会为 Spring HATEOAS 提供自动配置,该配置适用于大多数应用程序。自动配置取代了使用@EnableHypermediaSupport
和注册许多 bean 的需要,以简化构建基于超媒体的应用程序,包括一个LinkDiscoverers
(用于客户端支持)和一个ObjectMapper
配置为正确地将响应编组到所需表示形式的 bean。它ObjectMapper
是通过设置各种spring.jackson.*
属性来定制的,或者如果存在的话,通过Jackson2ObjectMapperBuilder
bean 来定制。
您可以使用 来控制 Spring HATEOAS 的配置@EnableHypermediaSupport
。请注意,这样做会禁用ObjectMapper
前面描述的自定义。
spring-boot-starter-hateoas 特定于 Spring MVC,不应与 Spring WebFlux 结合使用。为了将 Spring HATEOAS 与 Spring WebFlux 结合使用,您可以添加org.springframework.hateoas:spring-hateoas 对 with 的直接依赖项spring-boot-starter-webflux 。
|