在 Spring Boot 中校验用户上传的图片文件

图片上传是现代应用中非常常见的一种功能,也是风险比较高的一个地方。恶意用户可能会上传一些病毒、木马。这些东西不仅严重威胁服务器的安全还浪费了带宽,磁盘等资源。所以,在图片上传的接口中,一定要对用户上传的文件进行严格的校验。 本文介绍了 2 种对图片文件进行验证的方法可供你参考。 文件后缀校验 通过文件后缀(也就是文件扩展名,通常用于表示文件的类型),进行文件类型校验这是最常见的做法。 图片文件的后缀类型有很多,常见的只有:jpg、jpeg、gif、png、webp。我们可以在配置或者代码中定义一个“允许上传的图片后缀”集合,用于校验用户上传的图片文件。 package cn.springdoc.demo.web.controller; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Set; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/upload") public class UploadController { // 允许上传的图片类型的后缀集合 static final Set<String> imageSuffix = Set.of("jpg", "jpeg", "gif", "png", "webp"); @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<String> upload (@RequestParam("file") MultipartFile file ) throws IllegalStateException, IOException{ // 文件的原始名称 String fileName = file.getOriginalFilename(); if (fileName == null) { return ResponseEntity.

Spring 的 beanName 设置可能会导致代理失败

一些使用小细节就是在不断的源码探索中逐步发现的,今天就来和大家研究一下通过 beanName 的设置,可以让一个 bean 拒绝被代理的问题! 1. 代码实践 假设我有如下一个切面: @Aspect @EnableAspectJAutoProxy @Component public class LogAspect { @Pointcut("execution(* org.javaboy.demo.service.*.*(..))") public void pc() { } @Before("pc()") public void before(JoinPoint jp) { String name = jp.getSignature().getName(); System.out.println(name + " 方法开始执行了..."); } } 这个切面要拦截的方法是 org.javaboy.demo.service 包下的所有类的所有方法,现在,这个包下有一个 BookService 类,内容如下: @Service("org.javaboy.demo.service.BookService.ORIGINAL") public class BookService { public void hello() { System.out.println("hello bs"); } } 这个 BookService 的 beanName 我没有使用默认的 beanName,而是自己配置了一个 beanName,这个 beanName 的配置方式是 类名的完整路径 + .ORIGINAL。 当我们按照这样的规则给 bean 取名之后,那么即使当前 bean 已经包含在切点所定义的范围内,这个 bean 也不会被代理了。

在 Spring Cloud Gateway 中修改响应体

1、简介 本文将带你了解如何在 Spring Cloud Gateway 中读取、修改响应体,然后再响应给客户端。 2、Spring Cloud Gateway 快速回顾 Spring Cloud Gateway(简称 SCG)是 Spring Cloud 系列中的一个子项目,它提供了一个构建在响应式 Web 栈之上的 API 网关。关于它的更多详细信息和用法,你可以参考 官方文档。 设计 API Gateway 解决方案时经常出现的一种特殊使用场景:如何在将后端响应的 Body 发送回客户端之前对其进行处理? 下面列出了一些可能会用到这种功能的场景: 保持与现有客户端的兼容性,同时允许后台不断迭代 需要屏蔽响应中的某些敏感字段(脱敏) 实现这个需求,只需要实现一个 Filter 来处理后台响应即可。Filter 是 SCG 的核心概念。 Filter 组件创建后就可以将其应用于任何已声明的路由(Route)。 3、实现数据过滤 Filter 创建一个简单的 Filter 来屏蔽 JSON 响应中的某些值。 例如,给定的 JSON 有一个名为 “ssn” 的字段: { "name" : "John Doe", "ssn" : "123-45-9999", "account" : "9999888877770000" } 我们希望用一个固定的值进行替换,从而防止数据泄漏: { "name" : "John Doe", "ssn" : "****", "account" : "9999888877770000" } 3.

使用 Spring 注解实例化同一个类的多个 Bean

1、概览 Spring IoC 容器创建和管理 Spring Bean,这些 Bean 是应用的核心。创建一个 Bean 实例与从普通的 Java 类创建对象相同。然而,生成多个相同类的 Bean 可能会比较麻烦一点。 本文将带你了解如何在 Spring 中使用注解创建同一个类的多个 Bean。 2、使用 Java 配置 这是使用注解创建多个同类 Bean 的最简单易行的方法。 举一个简单的例子。我们有一个 Person 类,它有两个字段:firstName 和 lastName: public class Person { private String firstName; private String lastName; public Person(String firstName, String secondName) { super(); this.firstName = firstName; this.lastName = secondName; } @Override public String toString() { return "Person [firstName=" + firstName + ", secondName=" + lastName + "]"; } } 接下来,构建一个名为 PersonConfig 的配置类,并在其中定义 Person 类的多个 Bean:

使用 Testcontainers 测试 Redis

1、概览 Testcontainers 是一个用于创建临时 Docker 容器进行单元测试的 Java 库。当我们想要避免使用实际服务器进行测试时,它非常有用。 本文将会带你了解如何在 Spring Boot 中使用 Testcontainers 测试 Redis。 2、项目设置 使用任何测试容器的首要前提是在运行测试的机器上安装 Docker。 安装好 Docker 后,就可以开始设置 Spring Boot 应用了。 在此应用中,我们将设置一个 Redis Hash、一个 Repository 和一个使用 Repository 与 Redis 交互的 Service。 2.1、依赖 添加所需的 spring-boot-starter-test 和 spring-boot-starter-data-redis 依赖。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 接着,还要添加 Testcontainers 依赖: <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.17.2</version> <scope>test</scope> </dependency> 2.2、配置 在 application.properties 文件中添加 Redis 连接的详细信息: spring.redis.host=127.0.0.1 spring.redis.port=6379 3、应用设置 我们要创建一个小型应用,向 Redis 数据库读写 Product(产品)。 3.1、Entity 创建 Product 类。

使用 Testcontainers 对 Keycloak 进行集成测试

1、简介 通常我们会通过集成测试来验证应用功能是否正常。集成测试至关重要,特别是对于认证这种敏感且重要的功能。Testcontainers 允许在测试阶段启动 Docker 容器,以便对实际的技术栈运行测试。 本文将带你了解如何使用 Testcontainers 针对实际的 Keycloak 实例设置集成测试。 2、配置 Spring Security 和 Keycloak 我们需要配置 Spring Security、Keycloak 和 Testcontainers。 2.1、整合 Spring Boot 和 Spring Security 在 pom.xml 中添加 spring-boot-starter-security 依赖。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> 接着,创建一个示例 Controller,它返回一个 User。 @RestController @RequestMapping("/users") public class UserController { @GetMapping("me") public UserDto getMe() { return new UserDto(1L, "janedoe", "Doe", "Jane", "jane.doe@baeldung.com"); } } 至此,我们有了一个受保护的 Controller,用于处理对 /users/me 端点的请求。启动应用时,Spring Security 会为用户 user 生成一个密码,该密码在控制台输出的日志中。 2.配置 Keycloak 启动本地 Keycloak 的最简单方法是使用 Docker。

使用 @NotNull 注解进行非空校验

1、概览 空指针异常 NullPointerException 是一个常见问题,避免这种问题的方法之一是在方法参数上添加 @NotNull 等校验注解。 给方法参数添加了 @NotNull 注解后,还需要其他的一些设置才能自动对参数进行非空校验。 2、给方法参数添加 @NotNull 注解 创建一个类,其中包含一个返回 String 长度的方法。 在 String 参数上添加 @NotNull 注解: public class NotNullMethodParameter { public int validateNotNull(@NotNull String data) { return data.length(); } } 注意,有多个包下都有 @NotNull 注解,我们使用的应该是 jakarta.validation.constraints 包。 创建 NotNullMethodParameter 实例,然后使用 null 参数调用方法。 NotNullMethodParameter notNullMethodParameter = new NotNullMethodParameter(); notNullMethodParameter.doesNotValidate(null); 尽管在参数上使用了 @NotNull,但还是出现了空指针异常:NullPointerException。 注解未生效,因为没有 Validator 来执行它。 3、添加 Validator 添加 Hibernate Validator(jakarta.validation 的实现)来识别 @NotNull。 <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>8.0.0.Final</version> </dependency> 使用默认的 ValidatorFactory 创建 validator。

Spring Boot 中的静态资源配置

Spring Boot 对静态资源提供了支持。默认情况下,以下目录为默认的静态资源目录。其中的静态资源可以被直接访问: classpath:/META-INF/resources/ classpath:/resources/ classpath:/static/ classpath:/public/ ${user.dir}/public/ (程序运行目录下的 public 目录) 优先级从上往下,当多个静态资源目录中出现同名文件时,越靠上的目录权重越高。 静态资源的处理类是 ResourceHttpRequestHandler,它会正确地处理资源的 Last-Modified 响应和 Range 请求。 静态资源和 RequestMapping 冲突 如果静态资源路径和 @RequestMapping 路径冲突,则 @RequestMapping 优先。 例如,有如下 Controller: @RestController @RequestMapping public class DemoController { @GetMapping("/foo") public ResponseEntity<String> foo () { // 返回字符串 “controller” return ResponseEntity.ok("controller"); } } 在 src/main/resources/public 目录下有一个名为 foo 的文本文件,内容如下: public 启动应用,访问 http://localhost:8080/foo: $ curl http://localhost:8080/foo controller 你可以看到,响应的内容是 controller,说明 controller 的 @RequestMapping 优先。 另外,对于这种没有后缀、未知类型的静态资源,Spring Boot 会以 “下载” 的形式响应给客户端(添加了 Content-Disposition 响应头)。

springdoc-openapi 定义全局的默认 SecurityScheme

1、概览 本文将带你了解如何在 Spring MVC Web 应用中使用 springdoc-openapi 配置默认的全局 Security Scheme,并将其应用为 API 的默认安全配置,以及如何覆盖这些默认的安全配置。 OpenAPI 规范 允许为 API 定义一套 Security Scheme。可以配置 API 全局的安全配置,也可以按端点应用/删除安全配置。 2、设置 构建一个 Spring Boot Web 项目,使用 Maven。 2.1、依赖 该示例有两个依赖。第一个是 spring-boot-starter-web,用于构建 Web 应用。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.7.1</version> </dependency> 另一个依赖是 springdoc-openapi-ui,它用于输出 HTML、JSON 或 YAML 格式的 API 文档: <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-ui</artifactId> <version>1.6.9</version> </dependency> 2.2、启动类 使用 @SpringBootApplication 注解来定义启动类,通过 SpringApplication 类来启动应用: @SpringBootApplication public class DefaultGlobalSecuritySchemeApplication { public static void main(String[] args) { SpringApplication.run(DefaultGlobalSecuritySchemeApplication.class, args); } } 3、springdoc-openapi 基础配置 配置好 Spring MVC 后,来看看 API 语义信息。

WebClient 将 Flux 读取到 InputStream 中

1、概览 本文将会带你了解在 Java 响应式编程中如何将 Flux<DataBuffer> 读取到 InputStream。 2、请求设置 首先,使用 Spring Reactive WebClient 发起 GET 请求。使用由 gorest.co.in 托管的公共 API 端点来进行测试: String REQUEST_ENDPOINT = "https://gorest.co.in/public/v2/users"; 接下来,定义 getWebClient() 方法,用于获取 WebClient 类的新实例: static WebClient getWebClient() { WebClient.Builder webClientBuilder = WebClient.builder(); return webClientBuilder.build(); } 至此,我们就可以向 /public/v2/users 端点发出 GET 请求了。注意,必须以 Flux<DataBuffer> 对象的形式获取响应体。 3、BodyExtractors 和 DataBufferUtils 我们可以使用 spring-webflux 中 BodyExtractors 类的 toDataBuffers() 方法将响应体提取到 Flux<DataBuffer> 中。 将 body 构建为 Flux<DataBuffer> 类型的实例: Flux<DataBuffer> body = client .get( .uri(REQUEST_ENDPOINT) .exchangeToFlux( clientResponse -> { return clientResponse.