springboot升级到3.x版本.md

Spring Boot 2.7.4 升级到 3.x 涉及 Spring Security 的注意事项

背景

由于公司现在其他团队都升了springboot3.x的版本,导致我们要接入他们的client有点不太兼容,为了追上时代的列车,我决定把我们的项目也跟着升级才行。从 Spring Boot 2.7 升级到 3.x 是一个较大的版本跳跃,特别是 Spring Security 部分有一些重大变化。以下是需要注意的主要事项和一些资源:

主要变化

1、java 版本要求

  • Spring Boot 3.x 需要 Java 17 或更高版本

2、Jakarta EE迁移

  • 所有 javax.* 包已迁移到 jakarta.*
  • 影响包括 HttpServletRequest, HttpServletResponse 等类的导入

3、Spring Security 6.x 变化

  • WebSecurityConfigurerAdapter 已完全移除,必须使用基于组件的配置
  • 默认的 CSRF 保护行为有所变化
  • 默认的 CSRF 保护行为有所变化(显示配置)
  • 一些过时的方法和类被移除

4、依赖变化

  • 检查所有与安全相关的依赖是否兼容 Spring Boot 3.x

代码示例

旧配置:(基于 WebSecurityConfigurerAdapter)

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}

新写法:(Springboot3.x)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
return http.build();
}
}

看着是不是很简单?3是用bean去直接注入,然后换了一些api的写法。但是如果项目用到了OAuth2.0的话呢,就要做多一些事情了

旧配置:OAuth2.0 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Value("${SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURIS}")
private String issuerUris;

private List<String> issuersList = new ArrayList<>();

@Override
public void init(WebSecurity web) throws Exception {
issuersList = Arrays.asList(issuerUris.split(","));
super.init(web);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver(issuersList);
http
.authorizeRequests()
.antMatchers( "/test","/documents").permitAll()
.antMatchers("/swagger-ui/**", "/swagger-resources/**", "/webjars/**", "/v3/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));
}

@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/actuator/**");
}
}

新配置:OAuth2.0配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
import org.springframework.security.web.SecurityFilterChain;

import java.util.Arrays;
import java.util.List;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // 替代 @EnableGlobalMethodSecurity
public class SecurityConfiguration {

@Value("${SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURIS}")
private String issuerUris;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
List<String> issuersList = Arrays.asList(issuerUris.split(","));
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(issuersList);

http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/test", "/documents").permitAll()
.requestMatchers("/swagger-ui/**", "/swagger-resources/**",
"/webjars/**", "/v3/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.authenticationManagerResolver(authenticationManagerResolver)
);

return http.build();
}

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring().requestMatchers("/actuator/**");
}
}

变更说明

1、移除**WebSecurityConfigurerAdapter** 继承
  • Spring Security 6 完全移除了这个类
  • 改为使用 @Bean 方法配置

2、注解变化

  • @EnableGlobalMethodSecurity 改为 @EnableMethodSecurity

3、配置方法变化

  • configure(HttpSecurity http) 改为返回 SecurityFilterChain@Bean 方法
  • configure(WebSecurity web) 改为返回 WebSecurityCustomizer@Bean 方法

4、API 方法名称变化

  • authorizeRequests()authorizeHttpRequests()
  • antMatchers()requestMatchers()
  • ignoring().antMatchers()ignoring().requestMatchers()

5、初始化逻辑变化

  • 原来的 init() 方法中的逻辑可以移到 securityFilterChain 方法中
  • 或者使用 @PostConstruct 初始化字段

image-20250606165228894

6、路径问题

在 Spring Boot 3.4.1 中,如果 URL 末尾加 / 导致 No static resource 错误,通常是因为 静态资源处理规则路径匹配策略 发生了变化。以下是解决方案:

  1. Spring Boot 3.x 默认路径匹配行为调整
    • 旧版(如 2.x)会自动处理 /path/path/ 为相同路径。
    • Spring Boot 3.x 更严格/path/path/ 可能被视为不同路由。
  2. 静态资源映射规则
    • 如果请求的路径被误判为静态资源请求(如 api/frm/ecm/documents/),但实际是动态接口,Spring 会尝试查找静态文件,导致 No static resource 错误。

调整路径匹配策略

如果路径影响不多,可以直接

1
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE,value = {"", "/"})

如果路径很多的话,建议加个filter类去处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.fwk.ecm.documentprocessor.config;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class UrlNormalizationConfig {

@Bean
public FilterRegistrationBean<UrlNormalizationFilter> urlNormalizationFilter() {
FilterRegistrationBean<UrlNormalizationFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new UrlNormalizationFilter());
registration.addUrlPatterns("/api/*");
return registration;
}

static class UrlNormalizationFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String path = request.getRequestURI();
String query = request.getQueryString();


if ("GET".equalsIgnoreCase(request.getMethod()) && shouldNormalize(path)) {
String normalizedPath = path.replaceAll("/+$", "") + (query != null ? "?" + query : "");
((HttpServletResponse) res).sendRedirect(normalizedPath);
return;
}

if (shouldNormalize(path)) {
String normalizedPath = path.replaceAll("/+$", "");
req = new CustomHttpServletRequestWrapper((HttpServletRequest) req, normalizedPath);
}
chain.doFilter(req, res);
}

private boolean shouldNormalize(String path) {
return path.matches("/api/.+/") &&
!path.startsWith("/actuator/") &&
!path.contains(".");
}
}
static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String newPath;
public CustomHttpServletRequestWrapper(HttpServletRequest request, String newPath) {
super(request);
this.newPath = newPath;
}
@Override
public String getRequestURI() {
return newPath;
}
}
}

7、boot、cloud版本匹配

官方参考链接

  1. Spring Cloud 官方版本说明
    📌 Spring Cloud Release Train
    (查看最新版本与 Spring Boot 兼容性)
  2. Spring Boot 官方文档
    📌 Spring Boot Version Support
    (LTS 版本与维护周期)
  3. Spring Initializr(自动匹配版本)
    🛠 start.spring.io
    (创建项目时自动选择兼容版本)
Spring Cloud 版本 Spring Boot 版本 说明
2025.0.x 3.5.x Northfields
2024.0.x 3.4.x Moorgate
2023.0.x 3.2.x ~ 3.3.x Leyton
2022.0.x 3.0.x ~ 3.1.x Kilburn

推荐组合(生产环境)

  • Spring Boot 3.4.x + Spring Cloud 2024.0.x
  • Spring Boot 3.2.x + Spring Cloud 2023.0.x(长期支持)

springboot升级到3.x版本.md
https://liu-cj25.github.io/2025/06/06/springboot升级到3-x版本-md/
Author
cj
Posted on
June 6, 2025
Licensed under