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
代码示例 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) 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/**" ); } }
变更说明
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 初始化字段
6、路径问题 在 Spring Boot 3.4.1 中,如果 URL 末尾加 / 导致 No static resource 错误,通常是因为 静态资源处理规则 或 路径匹配策略 发生了变化。以下是解决方案:
Spring Boot 3.x 默认路径匹配行为调整
旧版(如 2.x)会自动处理 /path 和 /path/ 为相同路径。
Spring Boot 3.x 更严格 ,/path 和 /path/ 可能被视为不同路由。
静态资源映射规则
如果请求的路径被误判为静态资源请求(如 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版本匹配 官方参考链接
Spring Cloud 官方版本说明 📌 Spring Cloud Release Train (查看最新版本与 Spring Boot 兼容性)
Spring Boot 官方文档 📌 Spring Boot Version Support (LTS 版本与维护周期)
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 (长期支持)