文章目录
- [Spring Security 4.x / 5.x / 6.x 配置全对比:前后端不分离与前后端分离](#Spring Security 4.x / 5.x / 6.x 配置全对比:前后端不分离与前后端分离)
-
- [一、Spring Security 4.x 配置](#一、Spring Security 4.x 配置)
-
- [1.1 前后端不分离(Session + 表单登录)](#1.1 前后端不分离(Session + 表单登录))
- [1.2 前后端分离(JWT + 无状态)](#1.2 前后端分离(JWT + 无状态))
- [二、Spring Security 5.x 配置](#二、Spring Security 5.x 配置)
-
- [2.1 前后端不分离(5.7 之前,继承适配器 + Lambda DSL)](#2.1 前后端不分离(5.7 之前,继承适配器 + Lambda DSL))
- [2.2 前后端不分离(5.7+,SecurityFilterChain Bean)](#2.2 前后端不分离(5.7+,SecurityFilterChain Bean))
- [2.3 前后端分离(5.7 之前,继承适配器)](#2.3 前后端分离(5.7 之前,继承适配器))
- [2.4 前后端分离(5.7+,SecurityFilterChain Bean)](#2.4 前后端分离(5.7+,SecurityFilterChain Bean))
- [三、Spring Security 6.x 配置](#三、Spring Security 6.x 配置)
-
- [3.1 前后端不分离(Session + 表单登录)](#3.1 前后端不分离(Session + 表单登录))
- [3.2 前后端分离(JWT + 无状态)](#3.2 前后端分离(JWT + 无状态))
- 四、三个版本的核心差异
-
- [4.1 对比表](#4.1 对比表)
- [4.2 版本演进概括](#4.2 版本演进概括)
- [4.3 变与不变](#4.3 变与不变)
- 五、迁移时容易踩的点
Spring Security 4.x / 5.x / 6.x 配置全对比:前后端不分离与前后端分离
Spring Security 从 4.x 到 6.x,配置写法发生了明显变化。同一个需求------表单登录或 JWT 认证------在三个大版本里的写法差异很大。下面把 4.x、5.x、6.x 两个场景的配置逐版本列出来,再总结差异和迁移时容易踩的点。
一、Spring Security 4.x 配置
4.x 的配置类统一继承 WebSecurityConfigurerAdapter,重写 configure(HttpSecurity),用 .and() 在 HttpSecurity 上切换配置项。路径匹配用 antMatchers,授权方法用 authorizeRequests。
1.1 前后端不分离(Session + 表单登录)
java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/home")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login")
.permitAll();
// 前后端不分离 + Session,CSRF 保留
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("123456").roles("USER");
}
}
4.x 中每次切换配置项都要 .and() 回到 HttpSecurity,链式调用一长,可读性会下降。
1.2 前后端分离(JWT + 无状态)
4.x 时期前后端分离的 JWT 方案没有今天这样标准化,常见做法是关闭 CSRF、Session 设为 STATELESS,并把 JWT 过滤器插到表单登录过滤器之前。
java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
}
addFilterBefore 的插入位置在 4.x、5.x、6.x 中一致:放在 UsernamePasswordAuthenticationFilter 之前,让请求先尝试 Token 认证,再走表单认证。
二、Spring Security 5.x 配置
5.x 要分两段看。5.7 之前仍然以继承适配器为主,但 5.2 起 Lambda DSL 已经可用;5.7 起 WebSecurityConfigurerAdapter 被标记废弃,官方推荐改用 SecurityFilterChain Bean。因此每个场景给两种写法。
2.1 前后端不分离(5.7 之前,继承适配器 + Lambda DSL)
java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/login", "/css/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/home")
.permitAll()
);
}
}
2.2 前后端不分离(5.7+,SecurityFilterChain Bean)
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(auth -> auth
.antMatchers("/login", "/css/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/home")
.permitAll()
);
return http.build();
}
}
这里 authorizeRequests 和 antMatchers 仍是 5.x 的合法 API,只是配置入口从"继承"变成了 @Bean。
2.3 前后端分离(5.7 之前,继承适配器)
java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
}
2.4 前后端分离(5.7+,SecurityFilterChain Bean)
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeRequests(auth -> auth
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
STATELESS 的含义在 4.x、5.x、6.x 中没有变过:服务端不创建也不使用 HttpSession,每个请求必须自带凭据。
三、Spring Security 6.x 配置
6.x 中 WebSecurityConfigurerAdapter 被彻底移除,只能暴露 SecurityFilterChain Bean,以 Lambda DSL 为主。authorizeRequests 换成 authorizeHttpRequests,antMatchers 换成 requestMatchers,方法级注解从 @EnableGlobalMethodSecurity 换成 @EnableMethodSecurity。Java 基线到 17,Servlet API 从 javax.* 迁到 jakarta.*。
3.1 前后端不分离(Session + 表单登录)
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/css/**", "/js/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/home", true)
.failureUrl("/login?error")
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/login")
.permitAll()
);
// 前后端不分离 + Session 认证,不要禁用 CSRF
return http.build();
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
UserDetails user = User.withUsername("user")
.password(encoder.encode("123456"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
前后端不分离下 CSRF 不建议禁用,因为浏览器仍会自动携带 JSESSIONID,禁掉等于放弃一层防护。
3.2 前后端分离(JWT + 无状态)
java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private static final String[] PUBLIC_PATHS = {
"/api/auth/login",
"/api/auth/register",
"/actuator/health"
};
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers(PUBLIC_PATHS).permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(ex -> ex
.authenticationEntryPoint((req, res, e) ->
res.sendError(401, "Unauthorized"))
.accessDeniedHandler((req, res, e) ->
res.sendError(403, "Forbidden"))
);
return http.build();
}
}
JWT 过滤器骨架:
java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
// 验签、解析用户和权限
// 构造 UsernamePasswordAuthenticationToken 写入 SecurityContextHolder
}
chain.doFilter(request, response);
}
}
JWT 不靠 Cookie 自动携带,CSRF 攻击的前提不成立,所以前后端分离下关闭 CSRF 是常规做法;STATELESS 保证服务端不创建 Session,也是水平扩展的前提。
四、三个版本的核心差异
4.1 对比表
| 维度 | 4.x | 5.x | 6.x |
|---|---|---|---|
| Java 基线 | Java 7/8 | Java 8/11 | Java 17 |
| 配置入口 | 继承 WebSecurityConfigurerAdapter,重写 configure(HttpSecurity) |
5.7 前以继承适配器为主,5.7 起推荐 SecurityFilterChain Bean |
不再继承,必须暴露 SecurityFilterChain Bean |
| DSL 风格 | .and() 链式调用 |
.and() 为主,5.2 起 Lambda DSL 可用 |
Lambda DSL 为主 |
| 路径匹配 | antMatchers() |
antMatchers() / mvcMatchers() |
requestMatchers() |
| 请求授权 | authorizeRequests() |
authorizeRequests() |
authorizeHttpRequests() |
| 方法级注解 | @EnableGlobalMethodSecurity |
@EnableGlobalMethodSecurity |
@EnableMethodSecurity |
| Servlet 包名 | javax.servlet |
javax.servlet |
jakarta.servlet |
| 默认 SecurityContext 仓库 | HttpSessionSecurityContextRepository |
HttpSessionSecurityContextRepository |
DelegatingSecurityContextRepository |
4.2 版本演进概括
4.x 是"继承时代"。 配置类继承适配器,重写 configure,全程 .and()。前后端不分离用 authorizeRequests + formLogin,分离场景在这个骨架里关 CSRF、加 JWT 过滤器。
5.x 是"过渡与并存"。 继承适配器仍然能用,但 5.2 起 Lambda DSL 已经可用,5.7 起适配器被标记废弃。同一套需求可以写出两种形态:继承式或 Bean 式。
6.x 是"组件化 + Lambda DSL"。 适配器被移除,只能写 Bean;authorizeRequests 换 authorizeHttpRequests,antMatchers 换 requestMatchers;Java 基线到 17,Servlet API 全面迁到 jakarta.*。
4.3 变与不变
前后端不分离与分离的核心决策没有随版本变化:Session + 浏览器自动带 Cookie,就保留 CSRF;JWT 放在请求头 + STATELESS,就关闭 CSRF。addFilterBefore 的插入位置也一直是 UsernamePasswordAuthenticationFilter 之前。
变化的是"把这些决策写进配置类"的语法形态:配置入口从继承变成 Bean,DSL 从 .and() 变成 Lambda,方法名从 antMatchers 变成 requestMatchers。
五、迁移时容易踩的点
第一,5.x 代码直接放 6.x,报错链很集中。 WebSecurityConfigurerAdapter 找不到、antMatchers 找不到、authorizeRequests 找不到、javax.servlet 相关 import 找不到。这四个报错基本覆盖了 5→6 迁移的绝大多数问题。
第二,前后端分离第一次 POST 返回 403,先看 CSRF。 6.x 中 CSRF 默认仍然开启,不一定是权限配置出了问题。判断逻辑是:认证凭据是否由浏览器自动携带?Session/Cookie 就保留 CSRF,JWT 放 Authorization 头就可以禁用。
第三,5.7 到 5.8 阶段继承适配器还能编译,但行为可能不直观。 某些组合下自定义配置可能出现不生效的情况,迁 6.x 之前先把 SecurityFilterChain Bean 写好更稳。
第四,升级路径建议。 新项目直接用 Spring Boot 3.x + Security 6.x。老项目如果卡在 5.x,先升到 5.8,在 5.8 下就把 SecurityFilterChain Bean 和 Lambda DSL 写好,再迁 6.x 时只需要处理 jakarta 包名和少量 API 签名变化。反过来,直接拿 5.x 的配置代码去 6.x 里用,版本代差在这个框架上体现得非常明显。