Spring Security 中的 RequestRejectedException

1、简介 Spring 5.0 至 5.0.4、4.3 至 4.3.14 以及其他旧版本在 Windows 系统上存在目录或路径遍历安全漏洞。 静态资源配置错误会导致恶意用户访问服务器的文件系统。例如,在 Windows 上使用 file: 协议配置静态资源,可能导致用户非法访问文件系统。 Spring 承认存在该 漏洞,并在后续版本中对其进行了修复。 此修复可防止应用遭受路径遍历攻击。不过,在修复后,一些之前的 URL 现在会抛出 org.springframework.security.web.firewall.RequestRejectedException 异常。 本文先带你了解什么是 “路径遍历攻击”,在这个知识背景下再带你了解 org.springframework.security.web.firewall.RequestRejectedException 和 StrictHttpFirewall 的相关知识。 2、路径遍历漏洞 路径遍历或目录遍历漏洞可非法访问 Web 文档根目录以外的内容。例如,篡改 URL 可对文档根目录以外的文件进行未经授权的访问。 虽然大多数最新和流行的 Web 服务器都能抵消大部分攻击,但攻击者仍可使用特殊字符(如 ./、../)的 URL 编码来规避 Web 服务器的安全设置并获取非法访问权限。 OWASP 介绍了路径遍历漏洞和解决方法。 3、Spring 的漏洞 先尝试复现这个漏洞,然后再介绍如何进行修复。 首先,克隆 Spring Framework MVC 示例。然后,修改 pom.xml,用一个易受攻击的版本替换现有的 Spring Framework 版本。 克隆仓库: git clone git@github.com:spring-projects/spring-mvc-showcase.git 在克隆目录中,编辑 pom.xml,修改 Spring Framework 的版本为 5.0.0.RELEASE: <org.springframework-version>5.0.0.RELEASE</org.springframework-version> 接下来,编辑 Web 配置类 WebMvcConfig,修改 addResourceHandlers 方法,使用 file: 将资源映射到本地文件目录:

在 Spring Boot 中设置环境变量前缀

1、概览 本文将带你了解 Spring Boot 2.5 中的一个新特性:为系统环境变量指定前缀。通过该特性,就可以在同一环境中运行多个不同的 Spring Boot 应用,只要所有属性都使用带前缀的版本。 2、环境变量前缀 有时,我们可能需要在同一个环境中运行多个 Spring Boot 应用,并且经常会面临环境变量名称分配的问题。现在,我们可以在应用级别设置一个 “前缀”,不同应用加载不同前缀的环境变量即可。 以一个简单的 Spring Boot 应用为例,通过设置前缀来修改应用属性,例如 tomcat 服务器端口。 关于 Spring Boot 中属性属性的优先级,你可以参阅 中文文档。 2.1、Spring Boot 应用示例 创建一个 Spring Boot 应用。 首先,为应用设置一个前缀。为了简单起见,称之为 prefix: @SpringBootApplication public class PrefixApplication { public static void main(String[] args) { SpringApplication application = new SpringApplication(PrefixApplication.class); // 设置环境变量前缀 application.setEnvironmentPrefix("prefix"); application.run(args); } } 不能使用已经包含下划线字符(_)的单词作为前缀。否则,抛出异常。 再创建一个 API 端点,返回应用正在监听的端口: @Controller public class PrefixController { @Autowired private Environment environment; @GetMapping("/prefix") public String getServerPortInfo(final Model model) { model.

覆盖 Spring Boot 依赖的版本号

1、简介 Spring Boot 为大多数常用的依赖、第三方库都定义好了最兼容的版本号(如 JPA、MySQL 驱动、Redis 客户端)。得益于此,我们可以快速地创建一个新应用。 但,有时出于特殊原因,我们需要修改这些预定义的依赖版本号。 2、Spring Boot 依赖清单(BOM) Spring Boot 使用 Bill of Materials (BOM) 来定义依赖和版本。 大多数 Spring Boot 项目都继承自 spring-boot-starter-parent,而 spring-boot-starter-parent 本身又继承自 spring-boot-dependencies 。后者就是 Spring Boot BOM,它只是一个 Maven POM 文件,其中有一个很大的 ependencyManagement 节点: <dependencyManagement> <dependencies> <dependency> ... </dependency> <dependency> ... </dependency> </dependencies> </dependencyManagement> 通过使用 Maven 的 dependencyManagement,BOM 可以指定依赖的默认版本(如果应用使用了这个依赖)。 Spring Boot BOM 中的一个依赖如下: <dependency> <groupId>org.apache.activemq</groupId> <artifactId>activemq-amqp</artifactId> <version>${activemq.version}</version> </dependency> 这意味着,在项目中依赖了 ActiveMQ,就会使用这个默认的版本。 另外,注意。版本是使用属性占位符指定的。这是 Spring Boot BOM 中的常见做法,它在自己的 properties 部分中提供了该属性和其他属性的值。 3、覆盖 Spring Boot 管理的依赖版本 既然已经了解了 Spring Boot 如何管理依赖版本,那就来看看如何覆盖它们。

Spring Boot 3.2.0 中的 SSL 热重载功能

Spring Boot 3.2.0 为嵌入式 Web 服务器添加了热加载 SSL 证书和密钥的功能。这意味着无需重启应用就能替换 SSL 配置。Tomcat 和 Netty 嵌入式 Web 服务器支持热重载。 首先,使用 OpenSSL 创建 SSL 私钥和匹配证书: mkdir certs cd certs openssl req -x509 -subj "/CN=demo-cert-1" -keyout demo.key -out demo.crt -sha256 -days 365 -nodes -newkey ed25519 这会创建一个私钥,存储在 certs/demo.key 中,和一个与之匹配的(自签名)证书,通用名称为 demo-cert-1,存储在 certs/demo.crt 中。 现在创建一个新的 Spring Boot 3.2.0 应用,添加 Spring Web 依赖,默认情况下使用 Tomcat Web 服务器。你可以通过 start.springboot.io 快速创建。 在 application.yaml 配置文件中,添加如下配置: spring.ssl.bundle.pem: demo: reload-on-update: true keystore: certificate: "certs/demo.crt" private-key: "certs/demo.key" 这配置了一个 SSL Bundle,名称为 demo,并配置上文生成的证书和私钥。

Spring Boot 中的静态资源乱码

通过浏览器直接访问 Spring Boot 中的静态文件(如 js、css),如果静态文件包含中文的话则会显示乱码。 静态文件 在 src/main/resources/public 目录下创建一个 test.js 文件,用于测试: public 目录是 Spring Boot 默认的静态资源目录,里面的文件可以被客户端直接访问。 test.js 文件内容如下: // 输出中文内容 (function(){ console.log("你好 Spring Doc"); })(); 这个文件中包含两段中文内容。 测试 启动应用,打开浏览器访问上述 JS 文件:http://localhost:8080/test.js。 如你所见,文件中的中文内容全部乱码。尽管这个 js 文件本身就是 UTF-8 编码。 解决办法 究其原因,是因为服务器响应的 Content-Type: application/javascript 头中没有指定文本文件的编码类型。 我们可以在配置文件中对编码类型进行设置: server: servlet: encoding: force: true server.servlet.encoding.force 配置指定了是否在 HTTP 请求和响应中强制使用配置的字符集编码。默认为 false,这里设置为 true。 字符集编码可以通过 server.servlet.encoding.charset 属性进行配置,它默认就是 UTF-8。 重启应用,再次访问 test.js 文件: Content-Type Header 指定了正确的编码,文件中的中文内容已经正常显示了。

Spring Boot 中的 Startup Actuator 端点

1、概览 Spring Boot 的启动过程可能涉及到繁琐的资源初始化。本文将带你了解如何通过 Spring Boot Actuator 的 Startup 端点追踪和监控这些启动信息。 2、应用启动追踪 追踪应用启动过程中的各个步骤可以提供有用的信息,帮助我们了解应用启动各个阶段所花费的时间。这种工具还能提高我们对上下文生命周期和应用启动顺序的理解。 Spring 提供了 记录应用启动 功能。此外,Spring Boot Actuator 还通过 HTTP 或 JMX 提供了多种生产级监控和管理功能。 从 Spring Boot 2.4 开始,应用启动追踪指标可通过 /actuator/startup 端点获得。 3、设置 在 pom.xml 中添加 spring-boot-starter-actuator 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> 还需要 spring-boot-starter-web 依赖项,用于暴露 HTTP 端点: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.5.4</version> </dependency> 在 application.properties 文件中配置属性,在 HTTP 上公开所需的端点: management.endpoints.web.exposure.include=startup 最后,使用 curl 查询 Actuator 的 HTTP 端点,使用 jq 解析 JSON 响应。 4、Actuator 端点 为了捕获启动事件,需要使用 @ApplicationStartup 接口的实现来配置应用。默认情况下,管理应用生命周期的 ApplicationContext 使用 “空” 操作的实现。这显然不会执行启动检测和追踪,从而将开销降至最低。

OncePerRequestFilter 的用法

1、概览 本文将带你了解 Spring 中一种特殊类型的 Filter(过滤器)OncePerRequestFilter。 通过实例了解它的功能和用法。 2、OncePerRequestFilter 是什么? 回顾一下 Filter 的工作原理。Filter 可以在 Servlet 执行之前或之后调用。当请求被调度给一个 Servlet 时,RequestDispatcher 可能会将其转发给另一个 Servlet。另一个 Servlet 也有可能使用相同的 Filter。在这种情况下,同一个 Filter 会被调用多次。 但是,有时需要确保每个请求只调用一次特定的 Filter。一个常见的用例是在使用 Spring Security 时。当请求通过过滤器链(Filter Chain)时,对请求的身份证认证应该只执行一次。 在这种情况下,可以继承 OncePerRequestFilter。Spring 保证 OncePerRequestFilter 只对指定请求执行一次。 3、使用 OncePerRequestFilter 处理同步请求 定义一个继承了 OncePerRequestFilter 的 AuthenticationFilter Filter 类,并覆写 doFilterInternal() 方法: public class AuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String usrName = request.getHeader(“userName”); logger.info("Successfully authenticated user " + userName); filterChain.

在 Spring Webflux 中使用 @Cacheable 注解缓存结果

1、概览 本文将带你了解如何在 Spring WebFlux 中使用 @Cacheable 注解实现缓存,以及一些常见的问题和解决办法。 2、@Cacheable 和响应式类型 在本文撰稿时,@Cacheable 还不能和响应式框架无缝整合。主要问题在于,目前还没有非阻塞式的缓存实现(JSR-107 缓存 API 是阻塞式的)。只有 Redis 提供了响应式驱动。 虽然,仍然可以在方法上使用 @Cacheable。这会缓存封装对象(Mono 或 Flux),但不会缓存方法的实际结果。 2.1、项目设置 创建一个使用响应式 MongoDB 驱动的 Spring WebFlux 项目。并且用 Testcontainers 代替真实运行的 MongoDB 进行测试。 测试类使用 @SpringBootTest 进行注解,如下: final static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10")); @DynamicPropertySource static void mongoDbProperties(DynamicPropertyRegistry registry) { mongoDBContainer.start(); registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl); } 这几行代码会启动 MongoDB 实例,并将 URI 传递给 Spring Boot 以自动配置 Mongo Repository。 创建带有保存和获取 Item 方法的 ItemService 类: @Service public class ItemService { private final ItemRepository repository; public ItemService(ItemRepository repository) { this.

Spirng Boot 返回:415 Unsupported MediaType

1、概览 本文将带你了解如何解决在 Spring Boot API 返回状态码 “415 Unsupported MediaType” 的问题,以及出现该问题的原因。 2、背景 我们一个老客户要求我们给他的系统再开发一个桌面应用,类似于用户管理。新的桌面应用,直接调用原系统的 API 服务。 3、API 请求 通过 API 来获取所有用户: curl -X GET https://baeldung.service.com/user 成功获取响应。接着,获取一个单独的用户: curl -X GET https://baeldung.service.com/user/{user-id} 响应如下: { "id": 1, "name": "Jason", "age": 23, "address": "14th Street" } 一切也OK,根据响应,可以确定用户拥有以下参数:id、name、age 和 address。 现在,尝试添加新用户: curl -X POST -d '{"name":"Abdullah", "age":28, "address":"Apartment 2201"}' https://baeldung.service.com/user/ 结果,这次服务器响应了 HTTP 状态码为 415 的错误响应: { "timestamp": "yyyy-MM-ddThh:mm:ss.SSS+00:00", "status": 415, "error": "Unsupported Media Type", "path": "/user/" } 在弄清 “为什么会出现这个错误?” 之前,需要先弄清楚 “这个错误是什么?”。

在 Spring Boot 中使用 JSP

在前后端分离架构、SPA 应用大行其道的今天,模板引擎已经逐渐被淘汰了。更别提 JSP 这种上古模板引擎了。 Spring Boot 推荐使用 FreeMarker、Groovy、Thymeleaf 或者 Mustache 作为模板引擎。不推荐 JSP,主要是 JSP 的编译方式比价特殊,它需要先把 JSP 代码编译为 Servlet,最后通过执行 Servlet 来输出模板内容。 当然,在 Spring Boot 中使用 JSP 也是可以的,只需要些许配置即可。 创建 Spring Boot 项目 创建 Spring Boot(3.0.3)项目,在 pom.xml 中添加如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Servlet --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> </dependency> <!-- Tomcat 嵌入式 JSP 解析器 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!-- JSP jstl --> <dependency> <groupId>javax.servlet.jsp.jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> 注意,jstl 需要自己手动添加版本号,它没有被 Spring Boot 管理。 在 Spring Boot 支持的嵌入式容器中只有 Tomcat 支持使用 JSP,Undertow 和 Jetty 均不支持!