Spring Boot 非常适合 Web 应用程序开发。 您可以使用嵌入式 Tomcat、Jetty、Undertow 或 Netty 创建一个自包含的 HTTP 服务器。
大多数 Web 应用程序使用 spring-boot-starter-web
模块来快速启动和运行。您还可以选择使用 spring-boot-starter-webflux
模块构建响应式式 Web 应用程序。
如果您还没有开发过 Spring Boot Web 应用程序,可以关注 Getting started 章节中的 "Hello World!" 示例进行操作.
1. Servlet Web Applications
如果您想构建基于 servlet 的 web 应用程序,可以利用 SpringBoot 针对 SpringMVC 或 Jersey 的自动配置。
1.1. Spring Web MVC 框架
Spring Web MVC 框架 (通常简称 "Spring MVC") 是一个富模型-视图-控制器的 web 框架. Spring MVC 允许您创建 @Controller
或者 @RestController
bean 来处理传入的 HTTP 请求. 控制器中的方法通过 @RequestMapping
注解映射到 HTTP.
以下是一个使用了 @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);
}
}
“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();
}
}
@Component
public class MyUserHandler {
public ServerResponse getUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse getUserCustomers(ServerRequest request) {
...
return ServerResponse.ok().build();
}
public ServerResponse deleteUser(ServerRequest request) {
...
return ServerResponse.ok().build();
}
}
Spring MVC 是 Spring Framework 核心的一部分,详细介绍可参考其 参考文档. spring.io/guides 还提供了几个 Spring MVC 相关的指南.
提示:您可以定义任意数量的 RouterFunction
bean,以模块化路由器的定义。 如果您需要应用优先级,可以对 Bean 进行排序。
1.1.1. Spring MVC 自动配置
Spring Boot 提供了适用于大多数 Spring MVC 应用的自动配置 (auto-configuration) .
自动配置在 Spring 默认功能上添加了以下功能:
如果您想保留 Spring Boot MVC 的功能,并且需要添加其他 MVC configuration (interceptor、formatter 和视图控制器等) ,可以添加自己的 WebMvcConfigurerAdapter
类型的 @Configuration
类,但不能带 @EnableWebMvc
注解.
如果您想自定义 RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
或者 ExceptionHandlerExceptionResolver
实例,可以声明一个 WebMvcRegistrationsAdapter
实例来提供这些组件.
如果您想完全掌控 Spring MVC,可以添加自定义注解了 @EnableWebMvc
的 @Configuration
配置类. 或者添加自己的 @Configuration
注解的 DelegatingWebMvcConfiguration
,如 @EnableWebMvc
Javadoc 中的所述.
Spring MVC 使用了一种不同的 如果您想自定义 Spring MVC 使用的 |
1.1.2. HttpMessageConverters
Spring MVC 使用 HttpMessageConverter
接口来转换 HTTP 的请求和响应. 开箱即用功能包含了合适的默认值,比如对象可以自动转换为 JSON (使用 Jackson 库) 或者 XML (优先使用 Jackson XML 扩展,其次为 JAXB) . 字符串默认使用 UTF-8
编码.
如果您需要添加或者自定义转换器 (converter) ,可以使用 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);
}
}
上下文中的所有 HttpMessageConverter
bean 都将被添加到转换器列表中. 您也可以用这种方式来覆盖默认转换器.
1.1.3. 自定义 JSON 序列化和反序列化器
如果您使用 Jackson 序列化和反序列化 JSON 数据,您可能需要编写自己的 JsonSerializer
和 JsonDeserializer
类。
自定义序列化器通常是 通过注册 Jackson 模块,但 Spring Boot 提供了一个替代的 @JsonComponent
注解,可以更轻松地直接注册 Spring Bean .
您可以直接在 JsonSerializer
、JsonDeserializer
或 KeyDeserializer
实现上使用 @JsonComponent
注解。
您还可以在包含序列化器/反序列化器作为内部类的类上使用它,如以下示例所示:
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonSerializer<MyObject> {
@Override
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
jgen.writeStartObject();
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
jgen.writeEndObject();
}
}
public static class Deserializer extends JsonDeserializer<MyObject> {
@Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
String name = tree.get("name").textValue();
int age = tree.get("age").intValue();
return new MyObject(name, age);
}
}
}
ApplicationContext
中的所有 @JsonComponent
bean 都会自动注册到 Jackson。 因为 @JsonComponent
是用 @Component
元注解,所以适用于通常的组件扫描规则。
Spring Boot 还提供了 JsonObjectSerializer
和 JsonObjectDeserializer
基类,它们提供了有用的 序列化对象时标准 Jackson 版本的替代品。
有关详细信息,请参阅 Javadoc 中的 JsonObjectSerializer
和 JsonObjectDeserializer
。
上面的示例可以重写为使用 JsonObjectSerializer
/JsonObjectDeserializer
,如下所示:
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonObjectSerializer<MyObject> {
@Override
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
}
}
public static class Deserializer extends JsonObjectDeserializer<MyObject> {
@Override
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
JsonNode tree) throws IOException {
String name = nullSafeValue(tree.get("name"), String.class);
int age = nullSafeValue(tree.get("age"), Integer.class);
return new MyObject(name, age);
}
}
}
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 将在 classpath 或者 ServletContext
根目录下从名为 /static
(/public
、/resources
或 /META-INF/resources
) 目录中服务静态内容. 它使用了 Spring MVC 的 ResourceHttpRequestHandler
,因此您可以通过添加自己的 WebMvcConfigurer
并重写 addResourceHandlers
方法来修改此行为.
在一个独立的 (stand-alone) web 应用程序中,来自容器的默认 servlet t 未启用。 可以使用 configprop:server.servlet.register-default-servlet[] 属性启用它。
默认 servlet 为备选 servlet, 如果 Spring 决定不处理它,则从 ServletContext
的根目录提供内容. 大多情况下,这是不会发生的 (除非您修改了默认的 MVC 配置) ,因为 Spring 始终能通过 DispatcherServlet
来处理请求.
默认情况下,资源被映射到 /**
, 但可以通过 spring.mvc.static-path-pattern
属性调整. 比如,将所有资源重定位到 /resources/**
:
spring:
mvc:
static-path-pattern: "/resources/**"
您还可以使用 spring.web.resources.static-locations
属性来自定义静态资源的位置 (使用一个目录位置列表替换默认值) . 根 Servlet context path "/"
自动作为一个 location 添加进来.
除了上述提到的标准静态资源位置之外,还有一种特殊情况是用于 Webjars content. 如果以 Webjar 格式打包,则所有符合 /webjars/**
的资源都将从 jar 文件中服务.
如果您的应用程序要包成 jar,请不要使用 src/main/webapp 目录. 虽然此目录是一个通用标准,但它只适用于 war 打包,如果生成的是一个 jar,它将被绝大多数的构建工具所忽略.
|
Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许使用例如静态资源缓存清除 (cache busting) 或者 Webjar 版本无关 URL.
要使用 Webjar 版本无关 URL 功能,只需要添加 webjars-locator-core
依赖. 然后声明您的 Webjar,以 jQuery 为例,添加的 "/webjars/jquery/jquery.min.js"
将变成 "/webjars/jquery/x.y.z/jquery.min.js"
, 其中 x.y.z
是 Webjar 的版本.
如果您使用 JBoss,则需要声明 webjars-locator-jboss-vfs 依赖,而不是 webjars-locator-core ,否则所有 Webjar 将被解析成 404 .
|
要使用缓存清除功能,以下配置为所有静态资源配置了一个缓存清除方案,实际上是在 URL 上添加了一个内容哈希,例如 <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
:
spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
模板中的资源链接在运行时被重写,这得益于 ResourceUrlEncodingFilter 为 Thymeleaf 和 FreeMarker 自动配置. 在使用 JSP 时,您应该手动声明此过滤器. 其他模板引擎现在还不会自动支持,但可以与自定义模板宏 (macro) /helper 和 ResourceUrlProvider 结合使用.
|
当使用例如 Javascript 模块加载器动态加载资源时,重命名文件是不可选的. 这也是为什么支持其他策略并且可以组合使用的原因. "fixed" 策略将在 URL 中添加一个静态版本字符串,而不是更改文件名:
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"/>
) .
有关更多支持选项,请参阅 ResourceProperties
.
该功能已经在一个专门的 博客文章和 Spring 框架的参考文档中进行了详细描述. |
1.1.6. 欢迎页
Spring Boot 支持静态和模板化的欢迎页面. 它首先在配置的静态内容位置中查找 index.html
文件. 如果找不到,则查找 index
模板. 如果找到其中任何一个,它将自动用作应用程序的欢迎页面.
1.1.8. 路径匹配与内容协商
Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射相匹配,将传入的 HTTP 请求映射到处理程序 (例如 Controller 方法上的 @GetMapping
注解) .
Spring Boot 默认选择禁用后缀模式匹配,这意味着像 "GET /projects/spring-boot.json"
这样的请求将不会与 @GetMapping("/projects/spring-boot")
映射匹配. 这被视为是 Spring MVC 应用程序的最佳实践 . 此功能在过去对于 HTTP 客户端没有发送正确的 Accept 请求头的情况还是很有用的,我们需要确保将正确的内容类型发送给客户端. 如今,内容协商 (Content Negotiation) 更加可靠.
还有其他方法可以处理 HTTP 客户端发送不一致 Accept 请求头问题. 我们可以使用查询参数来确保像 "GET /projects/spring-boot?format=json"
这样的请求映射到 @GetMapping("/projects/spring-boot")
,而不是使用后缀匹配:
spring:
mvc:
contentnegotiation:
favor-parameter: true
或者,如果您更喜欢使用不同的参数名称:
spring:
mvc:
contentnegotiation:
favor-parameter: true
parameter-name: "myparam"
大多数标准媒体类型都是开箱即用的,但您也可以定义新的:
spring:
mvc:
contentnegotiation:
media-types:
markdown: "text/markdown"
后缀模式匹配已被弃用,并将在未来版本中删除. 如果您了解相关注意事项并仍希望应用程序使用后缀模式匹配,则需要以下配置:
spring:
mvc:
contentnegotiation:
favor-path-extension: true
pathmatch:
use-suffix-pattern: true
或者,不打开所有后缀模式,仅打开支持已注册的后缀模式更加安全:
spring:
mvc:
contentnegotiation:
favor-path-extension: true
pathmatch:
use-registered-suffix-pattern: true
从 Spring Framework 5.3 开始,Spring MVC 支持几种实现策略来将请求路径匹配到 Controller 处理程序. 它以前只支持 AntPathMatcher
策略,但现在也提供了 PathPatternParser
. Spring Boot 现在提供了一个可以在新策略中选择的配置属性:
spring:
mvc:
pathmatch:
matching-strategy: "path-pattern-parser"
有关为什么应该考虑这种新实现的更多详细信息,请查看 专门的博客文章。
PathPatternParser 是一个优化的实现,但限制了 某些路径模式变体 的使用,并且与后缀模式匹配( spring.mvc.pathmatch.use-suffix-pattern [已弃用], spring.mvc.pathmatch.use-registered-suffix-pattern [已弃用]) 或将 DispatcherServlet 映射为 Servlet 前缀( ( spring.mvc.servlet.path )
|
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
自动获取.
IntelliJ IDEA 根据您运行应用程序的方式来对 classpath 进行不同的排序. 在 IDE 中通过 main 方法来运行应用程序将导致与使用 Maven 或 Gradle 或来以 jar 包方式引用程序的排序有所不同,可能会导致 Spring Boot 找不到 classpath 中的模板. 如果您碰到到此问题,可以重新排序 IDE 的 classpath 来放置模块的 classes 和 resources 到首位.
|
1.1.11. 错误处理
默认情况下,Spring Boot 提供了一个使用了比较合理的方式来处理所有错误的 /error
映射,其在 servlet 容器中注册了一个全局错误页面. 对于机器客户端而言,它将产生一个包含错误、HTTP 状态和异常消息的 JSON 响应. 对于浏览器客户端而言,将以 HTML 格式呈现相同数据的 whitelabel 错误视图 (可添加一个解析到 error
的 View
进行自定义) .
如果要自定义默认错误处理行为,可以设置许多 server.error
属性.请参阅附录的 “Server Properties” 部分.
要完全替换默认行为,您可以实现 ErrorController
并注册该类型的 bean,或者简单地添加一个类型为 ErrorAttributes
的 bean 来替换内容,但继续使用现用机制.
BasicErrorController 可以作为自定义 ErrorController 的基类,这非常有用,尤其是在您想添加一个新的内容类型 (默认专门处理 text/html ,并为其他内容提供后备) 处理器的情况下. 要做到这点,您只需要继承 BasicErrorController 并添加一个带有 produces 属性的 @RequestMapping 注解的公共方法,之后创建一个新类型的 bean.
|
您还可以定义一个带有 @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;
}
}
以上示例中,如果同包下定义的控制器 SomeController
抛出了 MyException
,则将使用 MyErrorBody
类型的 POJO 来代替 ErrorAttributes
做 JSON 呈现.
在某些情况下,控制器级别处理的错误不会被 metrics infrastructure 记录。 应用程序可以通过将处理的异常设置为请求属性来确保将此类异常与请求指标一起记录:
@Controller
public class MyController {
@ExceptionHandler(CustomException.class)
String handleCustomException(HttpServletRequest request, CustomException ex) {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
return "errorView";
}
}
自定义错误页面
如果您想在自定义的 HTML 错误页面上显示给定的状态码,请将文件添加到 /error
目录中. 错误页面可以是静态 HTML (添加在任意静态资源目录下) 或者使用模板构建. 文件的名称应该是确切的状态码或者一个序列掩码.
例如,要将 404
映射到一个静态 HTML 文件,目录结构可以如下:
src/ +- main/ +- java/ | + <source code> +- resources/ +- public/ +- error/ | +- 404.html +- <other public assets>
使用 FreeMarker 模板来映射所有 5xx
错误,目录的结构如下:
src/ +- main/ +- java/ | + <source code> +- resources/ +- templates/ +- error/ | +- 5xx.ftlh +- <other templates>
对于更复杂的映射,您还通过可以添加实现了 ErrorViewResolver
接口的 bean 来处理:
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
new ModelAndView("myview");
}
return null;
}
}
您还可以使用常规的 Spring MVC 功能,比如 @ExceptionHandler
methods 方法和 @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"));
}
}
如果您注册了一个 ErrorPage ,它的路径最终由一个 Filter (例如,像一些非 Spring web 框架一样,比如 Jersey 和 Wicket) 处理,则必须将 Filter 显式注册为一个 ERROR dispatcher,如下示例:
|
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
}
请注意,默认的 FilterRegistrationBean
不包含 ERROR
调度器 (dispatcher) 类型.
部署 war 中的错误处理
当部署到 servlet 容器时, Spring Boot 使用其错误页面过滤器会将有错误状态的请求转发到相应的错误页面.这是必需的,因为 Servlet 规范没有提供用于注册错误页面的 API.根据要将 war 文件部署到的容器以及应用程序使用的技术,可能需要一些其他配置.
如果尚未提交响应,则只能将请求转发到正确的错误页面.默认情况下,WebSphere Application Server 8.0 及更高版本在成功完成 servlet 的 service 方法后提交响应. 您应该将 com.ibm.ws.webcontainer.invokeFlushAfterService
设置为 false
来禁用此行为.
如果您使用的是 Spring Security
,并且想访问错误页面中的用户 principal 信息,则必须配置 Spring Security
过滤器来处理的错误是分发.
为此,请将 spring.security.filter.dispatcher-types
属性配置为 async, error, forward, request
1.1.12. CORS 支持
Cross-origin resource sharing 跨域资源共享 (Cross-origin resource sharing,CORS) 是 most browsers 实现的一个 W3C specification ,其可允许您以灵活的方式指定何种跨域请求可以被授权,而不是使用一些不太安全和不太强大的方式 (比如 IFRAME 或者 JSONP) .
Spring MVC 从 4.2 版本起开始 支持 CORS. 您可在 Spring Boot 应用程序中使用 @CrossOrigin
注解 配置控制器方法启用 CORS.
还可以通过注册一个 WebMvcConfigurer
bean 并自定义 addCorsMappings(CorsRegistry)
方法来定义 全局 CORS 配置 :
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
1.2. JAX-RS 和 Jersey
如果您喜欢 JAX-RS 编程模型的 REST 端点,则可以使用一个实现来替代 Spring MVC. Jersey 和 Apache CXF 都能开箱即用. CXF 要求在应用程序上下文中以 @Bean
的方式将它注册为一个 Servlet
或者 Filter
. Jersey 有部分原生 Spring 支持,所以我们也在 starter 中提供了与 Spring Boot 整合的自动配置支持.
要使用 Jersey,只需要将 spring-boot-starter-jersey
作为依赖引入,然后您需要一个 ResourceConfig
类型的 @Bean
,您可以在其中注册所有端点:
@Component
public class MyJerseyConfig extends ResourceConfig {
public MyJerseyConfig() {
register(MyEndpoint.class);
}
}
Jersey 对于扫描可执行归档文件的支持是相当有限的. 例如,它无法扫描一个 完整的可执行 jar 文件中的端点,同样,当运行一个可执行的 war 文件时,它也无法扫描包中 WEB-INF/classes 下的端点. 为了避免该限制,您不应该使用 packages 方法,应该使用上述的 register 方法来单独注册每一个端点.
|
您可以注册任意数量实现了 ResourceConfigCustomizer
的 bean,以实现更高级的定制化.
所有注册的端点都应注解了 @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 在 ServletRegistrationBean
类型的 @Bean
中被设置为一个名为 jerseyServletRegistration
的 Servlet. 默认情况下,该 servlet 将被延迟初始化,您可以使用 spring.jersey.servlet.load-on-startup
自定义.
您可以禁用或通过创建一个自己的同名 bean 来覆盖该 bean. 您还可以通过设置 spring.jersey.type=filter
使用过滤器替代 servlet (该情况下, 替代或覆盖 @Bean
的为 jerseyFilterRegistration
) .
该过滤器有一个 @Order
, 您可以使用 spring.jersey.filter.order
设置.
当使用 Jersey 作为过滤器时,必须存在一个处理任何未被 Jersey 拦截的请求的 servlet。
如果您的应用程序不包含这样的 servlet,您可能希望通过将 server.servlet.register-default-servlet
设置为 true
来启用默认 servlet。
可以使用 spring.jersey.init.*
指定一个 map 类型的 property 以给定 servlet 和过滤器的初始化参数.
1.3. 内嵌 Servlet 容器支持
Spring Boot 包含了对内嵌 Tomcat, Jetty, 和 Undertow 服务器的支持. 大部分开发人员只需简单地使用对应的 Starter 来获取完整的配置实例. 默认情况下,内嵌服务器将监听 8080
上的 HTTP 请求.
1.3.1. Servlets, Filters, 与 listeners
使用内嵌 servlet 容器时,您可以使用 Spring bean 或者扫描方式来注册 Servlet 规范中的 Servlet、Filter 和所有监听器 (比如 HttpSessionListener
) .
将 Servlet、Filter 和 Listener 注册为 Spring Beans
任何 Servlet
、Filter
或 *Listener
的 Spring bean 实例都将被注册到内嵌容器中. 如果您想引用 application.properties
中的某个值,这可能会特别方便.
默认情况下,如果上下文只包含单个 Servlet,它将映射到 /
. 在多个 Servlet bean 的情况下,bean 的名称将用作路径的前缀. Filter 将映射到 /*
.
如果基于约定配置的映射不够灵活,您可以使用 ServletRegistrationBean
、FilterRegistrationBean
和 ServletListenerRegistrationBean
类来完全控制.
通常把过滤器 bean 无序是安全的. 如果需要特定的顺序,则应使用 @Order
注解 Filter
或使其实现 Ordered
. 您不能通过使用 @Order
注解 Filter
的bean方法来配置 Filter
的顺序.
如果您不能更改 Filter
类以添加 @Order
或实现 Ordered
,则必须为 Filter
定义一个 FilterRegistrationBean
并使用 setOrder(int)
方法设置注册bean的顺序.
则应避免在 Ordered.HIGHEST_PRECEDENCE
顺序点配置读取请求体的过滤器,因为它的字符编码可能与应用程序的字符编码配置不一致. 如果一个 Servlet 过滤器包装了请求,则应使用小于或等于 OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER
的顺序点对其进行配置.
要查看应用程序中每个过滤器的顺序,请为 web logging group (logging.level.web=debug ) 启用调试级别的日志记录. 然后,将在启动时记录已注册过滤器的详细信息,包括其顺序和 URL 模式. .
|
注册 Filter Bean 时要小心,因为它们是在应用程序生命周期中很早就初始化的. 如果需要注册与其他 bean 交互的 Filter ,请考虑改用 DelegatingFilterProxyRegistrationBean .
|
1.3.2. Servlet 上下文初始化
内嵌 servlet 容器不会直接执行 Servlet 3.0+ 的 javax.servlet.ServletContainerInitializer
接口或 Spring 的 org.springframework.web.WebApplicationInitializer
接口. 这是一个有意的设计决策,旨在降低在 war 内运行时第三方类库产生的风险,防止破坏 Spring Boot 应用程序.
如果您需要在 Spring Boot 应用程序中执行 servlet 上下文初始化,则应注册一个实现了 org.springframework.boot.context.embedded.ServletContextInitializer
接口的 bean. onStartup
方法提供了针对 ServletContext
的访问入口,如果需要,它可以容易作为现有 WebApplicationInitializer
的适配器.
1.3.3. ServletWebServerApplicationContext
Spring Boot 底层使用了一个不同的 ApplicationContext
类型来支持内嵌 servlet. ServletWebServerApplicationContext
是一个特殊 WebApplicationContext
类型,它通过搜索单个 ServletWebServerFactory
bean 来引导自身. 通常,TomcatServletWebServerFactory
、 JettyServletWebServerFactory
或者 UndertowServletWebServerFactory
中的一个将被自动配置.
通常,你不需要知道这些实现类. 大部分应用程序会自动配置,并为您创建合适的 ApplicationContext 和 ServletWebServerFactory .
|
在嵌入式容器设置中,ServletContext
被设置为服务器启动的一部分,这发生在应用程序上下文初始化期间。 因为 ApplicationContext
中的 bean 不能用 ServletContext
可靠地初始化。 解决这个问题的一种方法是注入 ApplicationContext
作为 bean 的依赖,并仅在需要时访问 ServletContext
。
另一种方法是在服务器启动后使用回调。 这可以使用监听 ApplicationStartedEvent
的 ApplicationListener
来完成,如下所示:
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
文件中定义这些属性.
常用服务器设置包括:
-
网络设置: 监听 HTTP 请求的端口 (
server.port
) ,绑定接口地址到server.address
等. -
会话设置: 是否持久会话 (
server.servlet.session.persistent
) 、session 超时 (server.servlet.session.timeout
) 、会话数据存放位置 (server.servlet.session.store-dir
) 和 session-cookie 配置 (server.servlet.session.cookie.*
) . -
错误管理: 错误页面位置 (
server.error.path
) 等.
Spring Boot 尽可能暴露通用的设置,但并不总是都可以. 针对这些情况,专用的命名空间为特定的服务器提供了自定义功能 (请参阅 server.tomcat
和 server.undertow
) . 例如,您可以使用内嵌 servlet 容器的特定功能来配置 access logs.
有关完整的内容列表,请参阅 ServerProperties 类.
|
SameSite Cookies
The SameSite
cookie attribute can be used by web browsers to control if and how cookies are submitted in cross-site requests.
The attribute is particularly relevant for modern web browsers which have started to change the default value that is used when the attribute is missing.
If you want to change the SameSite
attribute of your session cookie, you can use the configprop:server.servlet.session.cookie.same-site[] property.
This property is supported by auto-configured Tomcat, Jetty and Undertow servers.
It is also used to configure Spring Session servlet based SessionRepository
beans.
For example, if you want your session cookie to have a SameSite
attribute of None
, you can add the following to your application.properties
or application.yaml
file:
server:
servlet:
session:
cookie:
same-site: "none"
If you want to change the SameSite
attribute on other cookies added to your HttpServletResponse
, you can use a CookieSameSiteSupplier
.
The CookieSameSiteSupplier
is passed a Cookie
and may return a SameSite
value, or null
.
There are a number of convenience factory and filter methods that you can use to quickly match specific cookies.
For example, adding the following bean will automatically apply a SameSite
of Lax
for all cookies with a name that matches the regular expression myapp.*
.
@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
@Bean
public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
}
}
以编程方式自定义
如果您需要以编程的方式配置内嵌 servlet 容器,可以注册一个是实现了 WebServerFactoryCustomizer
接口的 Spring bean. WebServerFactoryCustomizer
提供了对 ConfigurableServletWebServerFactory
的访问入口,其中包含了许多自定义 setter 方法. 以下示例使用了编程方式来设置端口:
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
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()));
}
}
直接自定义 ConfigurableServletWebServerFactory
对于需要从 ServletWebServerFactory
扩展的更高级的用例,您可以自己暴露这种类型的 bean.
Setter 方法提供了许多配置选项. 还有几个 hook 保护方法供您深入定制. 有关详细信息,请参阅 源码文档.
自动配置的定制器仍会应用到您的定制工厂,因此请谨慎使用该选项. |
2. 响应死后 Web 应用程序
Spring Boot 通过为 Spring Webflux 提供自动配置来简化响应式 Web 应用程序的开发。
2.1. “Spring WebFlux Framework”
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);
}
}
“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();
}
}
@Component
public class MyUserHandler {
public Mono<ServerResponse> getUser(ServerRequest request) {
...
}
public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
...
}
public Mono<ServerResponse> deleteUser(ServerRequest request) {
...
}
}
WebFlux 是 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,可以将 @EnableWebFlux
注解到自己的 @Configuration
.
2.1.2. 使用 HttpMessageReader 和 HttpMessageWriter 作为 HTTP 编解码器
Spring WebFlux 使用 HttpMessageReader
和 HttpMessageWriter
接口来转换 HTTP 的请求和响应. 它们通过检测 classpath 中可用的类库,配置了 CodecConfigurer
生成合适的默认值.
Spring Boot 为编解码器提供了专用的配置属性 spring.codec.*
,还可以通过使用 CodecCustomizer
实例加强定制. 例如,spring.jackson.*
配置 key 应用于 Jackson 编解码器.
如果需要添加或自定义编解码器,您可以创建一个自定义的 CodecCustomizer
组件,如下所示:
@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {
@Bean
public CodecCustomizer myCodecCustomizer() {
return (configurer) -> {
configurer.registerDefaults(false);
configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
// ...
};
}
}
您还可以利用 Boot 自定义 JSON 序列化器和反序列化器.
2.1.3. 静态内容
默认情况下,Spring Boot 将在 classpath 或者 ServletContext
根目录下从名为 /static
(/public
、/resources
或 /META-INF/resources
) 目录中服务静态内容. 它使用了 Spring WebFlux 的 ResourceWebHandler
,因此您可以通过添加自己的 WebFluxConfigurer
并重写 addResourceHandlers
方法来修改此行为.
默认情况下,资源被映射到 /**
,但可以通过 spring.webflux.static-path-pattern
属性调整. 比如,将所有资源重定位到 /resources/**
:
spring:
webflux:
static-path-pattern: "/resources/**"
您还可以使用 spring.web.resources.static-locations
属性来自定义静态资源的位置 (使用一个目录位置列表替换默认值) ,如果这样做,默认的欢迎页面检测会切换到您自定义的位置. 因此,如果启动时有任何其中一个位置存在 index.html
,那么它将是应用程序的主页.
除了上述提到的标准静态资源位置之外,还有一种特殊情况是用于 Webjars 内容 . 如果以 Webjar 格式打包,则所有符合 /webjars/**
的资源都将从 jar 文件中服务.
Spring WebFlux 应用程序并不严格依赖于 Servlet API,因此它们不能作为 war 文件部署,也不能使用 src/main/webapp 目录.
|
2.1.4. 欢迎页面
Spring Boot 支持静态和模板化的欢迎页面. 它首先在配置的静态内容位置中查找 index.html
文件. 如果找不到,则查找 index
模板. 如果找到其中任何一个,它将自动用作应用程序的欢迎页面.
2.1.5. 模板引擎
除了 REST web 服务之外,您还可以使用 Spring WebFlux 来服务动态 HTML 内容. Spring WebFlux 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 Mustache.
Spring Boot 包含了以下的模板引擎的自动配置支持:
当您使用这些模板引擎的其中一个并附带了默认配置时,您的模板将从 src/main/resources/templates
自动获取.
2.1.6. 错误处理
Spring Boot 提供了一个 WebExceptionHandler
,它以合理的方式处理所有错误. 它在处理顺序中的位置紧接在 WebFlux 提供的处理程序之前,这些处理器排序是最后的. 对于机器客户端,它会生成一个 JSON 响应,其中包含错误详情、HTTP 状态和异常消息. 对于浏览器客户端,有一个 whitelabel 错误处理程序,它以 HTML 格式呈现同样的数据. 您还可以提供自己的 HTML 模板来显示错误 (请参阅 下一节) .
自定义此功能的第一步通常会沿用现有机制,但替换或扩充了错误内容. 为此,您可以添加 ErrorAttributes
类型的 bean.
想要更改错误处理行为,可以实现 ErrorWebExceptionHandler
并注册该类型的 bean. 因为 WebExceptionHandler
是一个非常底层的异常处理器,所以 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();
}
}
要获得更完整的功能,您还可以直接继承 DefaultErrorWebExceptionHandler
并覆盖相关方法.
在某些情况下,控制器级别处理的错误不会被 metrics infrastructure 记录。 应用程序可以通过将处理的异常设置为请求属性来确保将此类异常与请求指标一起记录:
@Controller
public class MyExceptionHandlingController {
@GetMapping("/profile")
public Rendering userProfile() {
// ...
throw new IllegalStateException();
}
@ExceptionHandler(IllegalStateException.class)
public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) {
exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build();
}
}
自定义错误页面
如果您想在自定义的 HTML 错误页面上显示给定的状态码,请将文件添加到 /error
目录中. 错误页面可以是静态 HTML (添加在任意静态资源目录下) 或者使用模板构建. 文件的名称应该是确切的状态码或者一个序列掩码.
例如,要将 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.7. Web 过滤器
Spring WebFlux 提供了一个 WebFilter
接口,可以通过实现该接口来过滤 HTTP 请求/响应消息交换. 在应用程序上下文中找到的 WebFilter
bean 将自动用于过滤每个消息交换.
如果过滤器的执行顺序很重要,则可以实现 Ordered
接口或使用 @Order
注解来指定顺序. Spring Boot 自动配置可能为您配置了几个 Web 过滤器. 执行此操作时,将使用下表中的顺序:
Web Filter | Order |
---|---|
|
|
|
|
|
|
2.2. 嵌入式 Reactive Server 支持
Spring Boot 包括对以下内嵌响应式 Web 服务器的支持: Reactor Netty、Tomcat、Jetty 和 Undertow. 大多数开发人员使用对应的 Starter 来获取一个完全配置的实例. 默认情况下,内嵌服务器在 8080 端口上监听 HTTP 请求.
2.3. Reactive Server 资源配置
在自动配置 Reactor Netty 或 Jetty 服务器时,Spring Boot 将创建特定的 bean 为服务器实例提供 HTTP 资源: ReactorResourceFactory
或 JettyResourceFactory
.
默认情况下,这些资源也将与 Reactor Netty 和 Jetty 客户端共享以获得最佳性能,具体如下:
-
用于服务器和客户端的的相同技术
-
客户端实例使用了 Spring Boot 自动配置的
WebClient.Builder
bean 构建.
开发人员可以通过提供自定义的 ReactorResourceFactory
或 JettyResourceFactory
bean 来重写 Jetty 和 Reactor Netty 的资源配置 —— 将应用于客户端和服务器.
您可以在 WebClient Runtime 章节中了解有关客户端资源配置的更多内容.
3. Graceful Shutdown(优雅关机)
所有四个嵌入式 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"
要配置超时时间,请配置 spring.lifecycle.timeout-per-shutdown-phase
属性,如以下示例所示:
spring:
lifecycle:
timeout-per-shutdown-phase: "20s"
如果 IDE 无法发送正确的 SIGTERM 信号,则在其 IDE 中使用正常关机可能无法正常工作. 有关更多详细信息,请参阅 IDE 的文档.
|
4. Spring Security
默认情况下,如果 Spring Security 在 classpath 上,则 Web 应用程序是受保护的. Spring Boot 依赖 Spring Security 的内容协商策略来确定是使用 httpBasic
还是 formLogin
.
要给 Web 应用程序添加方法级别的安全保护,可以使用 @EnableGlobalMethodSecurity
注解设置. 有关更多其他信息,您可以在 Spring Security 参考指南中找到.
默认的 UserDetailsService
只有一个用户. 用户名为 user
,密码是随机的,在应用程序启动时会以 INFO 级别打印出来,如下所示:
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 的级别设置为 INFO . 否则,默认密码不会打印出来.
|
您可以通过提供 spring.security.user.name
和 spring.security.user.password
来更改用户名和密码.
您在 Web 应用程序中默认会获得以下基本功能:
-
一个
UserDetailsService
(或 WebFlux 应用程序中的ReactiveUserDetailsService
) bean,采用内存存储形式,有一个自动生成密码的用户 (有关用户属性,请参阅SecurityProperties.User
) . -
用于整个应用程序 (如果 actuator 在 classpath 上,则包括 actuator 端点) 基于表单登录或 HTTP Basic 认证 (取决于
Accept
头) . -
一个用于发布身份验证事件的
DefaultAuthenticationEventPublisher
.
您可以通过为其添加一个 bean 来提供不同的 AuthenticationEventPublisher
.
4.1. MVC 安全
默认的安全配置在 SecurityAutoConfiguration
和 UserDetailsServiceAutoConfiguration
中实现. SecurityAutoConfiguration
导入用于 Web 安全的 SpringBootWebSecurityConfiguration
,UserDetailsServiceAutoConfiguration
配置身份验证,这同样适用于非 Web 应用程序. 要完全关闭默认的 Web 应用程序安全配置,可以添加 SecurityFilterChain
类型的 bean (这样做不会禁用 UserDetailsService
配置或 Actuator
的安全保护) .
要同时关闭 UserDetailsService
配置,您可以添加 UserDetailsService
、AuthenticationProvider
或 AuthenticationManager
类型的 bean. Spring Boot 示例中有几个使用了安全保护的应用程序,他们或许可以帮助到您.
可以通过添加自定义 SecurityFilterChain
或 WebSecurityConfigurerAdapter
来重写访问规则. 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
导入用于 Web 安全的 WebFluxSecurityConfiguration
,UserDetailsServiceAutoConfiguration
配置身份验证,这同样适用于非 Web 应用程序. 要完全关闭默认的 Web 应用程序安全配置,可以添加 WebFilterChainProxy
类型的 bean (这样做不会禁用 UserDetailsService
配置或 Actuator
的安全保护) .
要同时关闭 UserDetailsService
配置,您可以添加 ReactiveUserDetailsService
或 ReactiveAuthenticationManager
类型的 bean.
可以通过添加自定义 SecurityWebFilterChain
来重写访问规则. 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();
}
}
4.3. OAuth2
OAuth2 是 Spring 支持的一种广泛使用的授权框架.
4.3.1. 客户端
如果您的 classpath 上有 spring-security-oauth2-client
,则可以利用一些自动配置来轻松设置 OAuth2/Open ID Connect 客户端. 该配置使用 OAuth2ClientProperties
的属性. 相同的属性适用于 servlet 和响应式应用程序.
您可以在 spring.security.oauth2.client
前缀下注册多个 OAuth2 客户端和提供者 (provider) ,如下所示:
spring:
security:
oauth2:
client:
registration:
my-client-1:
client-id: "abcd"
client-secret: "password"
client-name: "Client for user scope"
provider: "my-oauth-provider"
scope: "user"
redirect-uri: "https://my-redirect-uri.com"
client-authentication-method: "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: "https://my-redirect-uri.com"
client-authentication-method: "basic"
authorization-grant-type: "authorization_code"
provider:
my-oauth-provider:
authorization-uri: "https://my-auth-server/oauth/authorize"
token-uri: "https://my-auth-server/oauth/token"
user-info-uri: "https://my-auth-server/userinfo"
user-info-authentication-method: "header"
jwk-set-uri: "https://my-auth-server/token_keys"
user-name-attribute: "name"
对于支持 OpenID Connect discovery 的 OpenID Connect 提供者,可以进一步简化配置. 需要使用 issuer-uri
配置提供者,issuer-uri
是其 Issuer Identifier 的 URI.
例如,如果提供的 issuer-uri 是 "https://example.com", 则将对 "https://example.com/.well-known/openid-configuration" 发起一个 OpenID Provider Configuration Request
. 期望结果是一个 OpenID Provider Configuration Response
. 以下示例展示了如何使用 issuer-uri
配置一个 OpenID Connect Provider:
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)
public class MyOAuthClientConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
http.oauth2Login((login) -> login.redirectionEndpoint().baseUri("custom-callback"));
return http.build();
}
}
Spring Boot 自动配置一个 InMemoryOAuth2AuthorizedClientService ,Spring Security 使用它来管理客户端注册. InMemoryOAuth2AuthorizedClientService 的功能有限,我们建议仅将其用于开发环境. 对于生产环境,请考虑使用 JdbcOAuth2AuthorizedClientService 或创建自己的 OAuth2AuthorizedClientService 实现.
|
OAuth2 客户端注册常见的提供者
对于常见的 OAuth2 和 OpenID 提供者 (provider) ,包括 Google、Github、Facebook 和 Okta,我们提供了一组提供者默认设置 (分别是 google
, github
, facebook
, 和 okta
等) .
如果您不需要自定义这些提供者,则可以将 provider
属性设置为您需要推断默认值的属性. 此外,如果客户端注册的 key 与默认支持的提供者匹配,则 Spring Boot 也会推断出来.
换而言之,以下示例中的两个配置使用了 Google 提供者:
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. 资源服务器
如果在 classpath 上有 spring-security-oauth2-resource-server
,只要指定了 JWK Set URI 或 OIDC Issuer URI,Spring Boot 就可以设置 OAuth2 资源服务器,如下所示:
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/"
如果授权服务器不支持 JWK 设置 URI,则可以使用用于验证 JWT 签名的公共密钥来配置资源服务器. 可以使用 spring.security.oauth2.resourceserver.jwt.public-key-location 属性来完成此操作,该属性值需要指向包含 PEM 编码的 x509 格式的公钥的文件.
|
相同的属性适用于 servlet 和响应式应用程序.
或者,您可以为 servlet 应用程序定义自己的 JwtDecoder bean,或为响应式应用程序定义 ReactiveJwtDecoder
.
如果使用不透明令牌而不是 JWT,则可以配置以下属性以通过自省来验证令牌:
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 没有提供 OAuth 2.0 授权服务器实现. 但此功能可从 Spring Security OAuth 项目获得,该项目最终会被 Spring Security 所取代. 在此之前,您可以使用 spring-security-oauth2-autoconfigure
模块轻松设置 OAuth 2.0 授权服务器,请参阅 其文档以获取详细信息.
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:
- 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"
identityprovider:
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"
identityprovider:
verification:
credentials:
- certificate-location: "path-to-other-verification-cert"
entity-id: "remote-idp-entity-id2"
sso-url: "https://remoteidp2.sso.url"
5. Spring Session
Spring Boot 为各种数据存储提供 Spring Session 自动配置. 在构建 Servlet Web 应用程序时,可以自动配置以下存储:
-
JDBC
-
Redis
-
Hazelcast
-
MongoDB
此外,https://github.com/spring-projects/spring-boot-data-geode[Spring Boot for Apache Geode] 提供了 使用 Apache Geode 作为会话存储的自动配置 。
Servlet 的自动配置取代了使用 @Enable*HttpSession
的需要.
构建响应式 Web 应用程序时,可以自动配置以下存储:
-
Redis
-
MongoDB
reactive 的自动配置取代了使用 @Enable*WebSession
的需要.
如果 classpath 上存在单个 Spring Session 模块,则 Spring Boot 会自动使用该存储实现. 如果您有多个实现,则必须选择要用于存储会话的 StoreType
. 例如,要使用 JDBC 作为后端存储,您可以按如下方式配置应用程序:
spring:
session:
store-type: "jdbc"
可以将 store-type 设置为 none 来禁用 Spring Session.
|
每个 store 都有自己的额外设置. 例如,可以为 JDBC 存储定制表的名称,如下所示:
spring:
session:
jdbc:
table-name: "SESSIONS"
可以使用 spring.session.timeout
属性来设置会话的超时时间. 如果未在 Servlet web application 设置该属性,则自动配置将使用 server.servlet.session.timeout
的值.
您可以使用 @Enable*HttpSession
(Servlet) 或 @Enable*WebSession
(Reactive) 来控制 Spring Session 的配置. 这将导致自动配置退出. 然后,可以使用注解的属性而不是先前描述的配置属性来配置 Spring Session.
6. Spring HATEOAS
如果您想开发一个使用超媒体 (hypermedia) 的 RESTful API,Spring Boot 提供的 Spring HATEOAS 自动配置在大多数应用程序都工作得非常好. 自动配置取代了 @EnableHypermediaSupport
的需要,
并注册了一些 bean,以便能轻松构建基于超媒体的应用程序,其包括了一个 LinkDiscoverers
(用于客户端支持) 和一个用于配置将响应正确呈现的 ObjectMapper
. ObjectMapper
可以通过设置 spring.jackson.*
属性或者 Jackson2ObjectMapperBuilder
bean (如果存在) 自定义.
您可以使用 @EnableHypermediaSupport
来控制 Spring HATEOAS 的配置. 请注意,这使得上述的自定义 ObjectMapper
被禁用.
spring-boot-starter-hateoas 是 Spring MVC 特有的,不应与 Spring WebFlux 结合使用。
为了将 Spring HATEOAS 与 Spring WebFlux 一起使用,您可以添加对 org.springframework.hateoas:spring-hateoas 和 spring-boot-starter-webflux 的直接依赖
|