Spring Security 7 来啦
官网地址:https://docs.spring.io/spring-security/reference/migration-7/configuration.html
Spring Security 7 引入了一些变化,尤其是在配置方式和注解的使用上。在 Spring Security 7 中,一些 API 和方法被移除了,新的方式加入了更多的 Java Config 支持。以下是一些主要的变更和改进,以及基于 Spring Security 7 的配置示例。
-
SecurityConfigurerAdapter 被弃用
SecurityConfigurerAdapter 在 Spring Security 7 中被弃用了,推荐使用 SecurityConfigurer 或直接通过 SecurityConfigurerAdapter 的子类来配置。 -
HttpSecurity 配置
在 Spring Security 7 中,对 HttpSecurity 的配置方式发生了一些变化,特别是在 formLogin(), httpBasic(), csrf() 等配置上。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable() // CSRF 保护禁用(取决于需求)
.authorizeRequests()
.antMatchers("/public/**").permitAll() // 公开路径不需要认证
.anyRequest().authenticated() // 其他路径需要认证
.and()
.formLogin() // 启用表单登录
.loginPage("/login") // 自定义登录页面
.permitAll() // 允许所有人访问登录页面
.and()
.logout() // 启用登出功能
.permitAll(); // 允许所有人访问登出
}
}
- AuthenticationManager 配置
Spring Security 7 中的 AuthenticationManager 配置方式与以前略有不同,Spring 7 中推荐使用 AuthenticationManagerBuilder 或直接在 SecurityFilterChain 中定义。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder
.inMemoryAuthentication()
.withUser(User.withUsername("user").password("{noop}password").roles("USER"))
.withUser(User.withUsername("admin").password("{noop}admin").roles("ADMIN"));
return authenticationManagerBuilder.build();
}
}
- 授权表达式支持
在 Spring Security 7 中,授权表达式(如 .hasRole(), .hasAuthority())被优化并可以在 HttpSecurity 中使用。
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 仅管理员可以访问
.antMatchers("/user/**").hasRole("USER") // 用户角色可以访问
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
- 异常处理和错误页面
Spring Security 7 提供了对错误页面和异常处理的更好的支持,可以使用 exceptionHandling() 配置来处理异常。
http
.exceptionHandling()
.accessDeniedPage("/403"); // 自定义403错误页面
- 基于 JWT 的认证
Spring Security 7 进一步加强了对 JWT(JSON Web Token)的支持。可以通过 BearerTokenAuthenticationFilter 来处理 JWT 认证。
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.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager())); // 添加 JWT 认证过滤器
}
}
Spring Security 7 的变化更多是围绕 Java 配置、内存用户存储和更灵活的认证机制,通常来说,它让开发者能够更方便地进行安全配置。
要深入理解 Spring Security 7,我们可以从几个关键领域探讨,包括 自定义身份验证流程、基于 JWT 的认证、授权机制的扩展、跨站请求伪造(CSRF)防护机制、OAuth2 的集成等。以下是一些进阶话题:
- 自定义认证流程
在 Spring Security 7 中,你可以完全自定义认证流程,特别是通过实现 AuthenticationProvider 和 AuthenticationManager 来定义自己的认证逻辑。
示例:自定义 AuthenticationProvider
AuthenticationProvider 负责验证用户身份。你可以根据需求自定义验证逻辑。
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
public CustomAuthenticationProvider(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 自定义认证逻辑,例如调用数据库验证
User user = (User) userDetailsService.loadUserByUsername(username);
if (user == null || !user.getPassword().equals(password)) {
throw new BadCredentialsException("Authentication failed for user: " + username);
}
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
注册 AuthenticationProvider
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.authenticationProvider(new CustomAuthenticationProvider(userDetailsService()));
return authenticationManagerBuilder.build();
}
- JWT 认证机制
Spring Security 7 对 JWT 的支持也非常强大。你可以通过自定义过滤器来实现基于 JWT 的认证。
JWT 认证流程
用户通过登录获取 JWT。
后续每次请求,客户端会在 HTTP 头部传递 JWT(Authorization: Bearer )。
Spring Security 使用 JWTAuthenticationFilter 来验证该 token。
JWT 过滤器
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JWTAuthenticationFilter extends OncePerRequestFilter {
private final AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = extractToken(request);
if (token != null && validateToken(token)) {
String username = getUsernameFromToken(token);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
private String extractToken(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7); // 获取 token
}
return null;
}
private boolean validateToken(String token) {
// 使用 JWT 库(如 JJWT 或 JWTDecoder)来验证 token 是否有效
return true;
}
private String getUsernameFromToken(String token) {
// 解析 token 获取用户名
return "username";
}
}
将 JWTAuthenticationFilter 集成到 SecurityFilterChain 中
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
- 授权扩展机制
Spring Security 提供了多种方法来扩展其授权机制,包括基于角色的权限控制和基于属性的权限控制。
基于角色的授权
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // 仅管理员可以访问
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 用户或管理员都可以访问
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
基于权限(hasPermission())的授权
Spring Security 允许通过权限(如 hasPermission())来实现更加细粒度的控制。
http
.authorizeRequests()
.antMatchers("/admin/**").hasPermission("ADMIN_ACCESS")
.antMatchers("/user/**").hasPermission("USER_ACCESS")
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
- CSRF 防护
Spring Security 默认启用了 CSRF 防护,但是在一些无状态(stateless)应用(例如基于 JWT 的应用)中,你可能会禁用它。
启用/禁用 CSRF
启用 CSRF:Spring Security 会自动生成一个 CSRF token,并验证每个请求中的 token。
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); // 使用 cookie 存储 token
禁用 CSRF:对于 REST API 或基于 JWT 的应用,通常禁用 CSRF。
http
.csrf().disable();
- OAuth2 集成
Spring Security 7 进一步改进了对 OAuth2 的集成,提供了更简洁的方式来进行 OAuth2 客户端配置。
OAuth2 客户端配置
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login/oauth2/**").permitAll()
.anyRequest().authenticated()
.and()
.oauth2Login(); // 启用 OAuth2 登录
return http.build();
}
}
OAuth2 资源服务器配置
如果你的应用作为 OAuth2 资源服务器(API 端点),你可以使用以下配置来验证 OAuth2 Access Token:
@Configuration
@EnableWebSecurity
public class OAuth2ResourceServerConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt(); // 配置 JWT 验证
return http.build();
}
}
Spring Security 7 提供了更强大、灵活且可扩展的认证与授权机制。通过自定义认证提供者、JWT 认证、OAuth2 集成等方式,可以轻松满足各种复杂的安全需求。如果你的应用涉及到特定的认证需求(如集成第三方认证或微服务中的统一认证),Spring Security 7 提供了更多的配置选项和扩展点。
Spring Security 7.0 版本的一个重要变化是完全移除了传统的链式配置(WebSecurityConfigurerAdapter 配置方式),而转向了全新的基于 Lambda 表达式的配置方式(Lambda DSL),这是一种更加简洁且灵活的方式来配置安全设置。Spring Security 7 采用了新的 API 设计,以便让开发者通过 Lambda 表达式更直观地进行配置。
在 Spring Security 7 之前,安全配置通常使用 WebSecurityConfigurerAdapter 类和 HttpSecurity 的链式方法来配置安全策略。但在 7.0 中,WebSecurityConfigurerAdapter 被弃用,并且很多配置方法都发生了变化。
新的 Lambda DSL 配置方式
Spring Security 7.0 推荐使用 SecurityFilterChain 配合 HttpSecurity 配置进行替代。
基本配置结构
传统的 WebSecurityConfigurerAdapter 配置方式:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login", "/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
新的 Lambda DSL 配置方式
Spring Security 7 推荐通过 SecurityFilterChain Bean 来配置安全策略,结合 HttpSecurity 使用 Lambda 风格的 API:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authz -> authz
.antMatchers("/login", "/public/**").permitAll()
.anyRequest().authenticated())
.formLogin(form -> form
.loginPage("/login")
.permitAll())
.logout(logout -> logout
.permitAll());
return http.build();
}
}
关键变化说明
SecurityFilterChain Bean:
在新的配置方式中,Spring Security 使用 SecurityFilterChain Bean 来定义安全策略。
http.build() 语法构建最终的 SecurityFilterChain 实例。
Lambda 表达式的使用:
authorizeRequests(), formLogin(), logout() 等配置方法都使用了 Lambda 表达式,使得代码更加简洁,且配置更具可读性。
@EnableWebSecurity:
@EnableWebSecurity 注解依然有效,告诉 Spring 启用 Web 安全功能,但不再需要扩展 WebSecurityConfigurerAdapter。
使用新的 Lambda DSL 的好处
更简洁和直观:不再需要继承和重写方法,而是直接使用 Lambda 表达式来配置。
灵活性:支持按需定制的安全配置,尤其适合微服务架构中的复杂安全需求。
减少样板代码:避免了传统方式中必须创建 WebSecurityConfigurerAdapter 类的样板代码,开发者可以直接在配置类中定义 SecurityFilterChain。
高级配置示例:基于角色的授权
如果需要进行更复杂的授权配置,如基于角色的授权,仍然可以轻松实现:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authz -> authz
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated())
.formLogin(form -> form
.loginPage("/login")
.permitAll())
.logout(logout -> logout
.permitAll());
return http.build();
}
高级配置示例:自定义认证和 JWT 认证
对于更复杂的认证逻辑,如 JWT 认证,仍然可以通过 Lambda DSL 来进行配置。
示例:基于 JWT 的认证配置
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests(authz -> authz
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated())
.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.formLogin().disable(); // 如果使用 JWT 则禁用表单登录
return http.build();
}
SecurityFilterChain 和 HttpSecurity 的兼容性
SecurityFilterChain 是一个函数式接口,它通过 Lambda 表达式支持配置多个 HTTP 安全策略。
HttpSecurity 的配置方法(如 authorizeRequests(), formLogin(), logout() 等)依然保留,并且支持 Lambda 风格的 API,提供了更为灵活的配置方式。
结论
Spring Security 7 完全移除了传统的 WebSecurityConfigurerAdapter 类,推荐开发者使用全新的 Lambda DSL 配置方式。通过这种方式,安全配置更加简洁、灵活,并且符合现代 Java 配置的标准,能够有效减少样板代码,同时提升可维护性。
通过这次的变更,Spring Security 更加注重“声明式配置”而非“继承式配置”,让开发者能够用更加直观的方式定制安全策略。如果你有更复杂的安全需求,新的 Lambda DSL 让扩展更加简单和灵活。
通俗易懂的来啦
Spring Security 7 中的 Lambda DSL 配置
- 简化配置:
在 Spring Security 7 中,使用 Lambda 表达式来配置 HTTP 安全性。这种方式更加简洁,易于理解,避免了传统配置中需要链式调用 .and() 的复杂性。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/blog/**").permitAll() // 允许所有访问博客页面
.anyRequest().authenticated() // 其他请求都需要认证
)
.formLogin(formLogin -> formLogin
.loginPage("/login") // 自定义登录页面
.permitAll() // 允许所有用户访问登录页面
)
.rememberMe(Customizer.withDefaults()); // 开启“记住我”功能
return http.build();
}
}
- 与传统配置方式的比较:
传统方式: 使用链式调用的方式来配置(例如 .and())。
http
.authorizeHttpRequests()
.requestMatchers("/blog/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.rememberMe();
Lambda DSL: 不再需要 .and() 方法,代码更加直观和简洁。
3. 自动化缩进:
Lambda DSL 的一个重要优势是自动的缩进和结构化,这使得配置更加可读和易于理解。
-
Customizer.withDefaults():
这个方法是 Spring Security 提供的一个快捷方式,用于启用默认的配置。例如,在启用 rememberMe 功能时,使用 Customizer.withDefaults() 会自动配置默认行为,而无需手动定义每个参数。 -
WebFlux 的配置:
WebFlux 安全配置也可以采用类似的 Lambda DSL 风格:
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/blog/**").permitAll()
.anyExchange().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(formLogin -> formLogin
.loginPage("/login")
);
return http.build();
}
}
- 自定义 DSL 的更新:
在 Spring Security 6.2 之后,HttpSecurity#apply() 方法已被弃用,并且计划在 Spring Security 7 中删除。推荐使用 .with() 方法来配置自定义 DSL,这会使配置更清晰和一致。
http.with(customSecurityDsl -> customSecurityDsl.configure(http));
Lambda DSL 的目标和优势
提高可读性: 自动缩进让配置更易读。
减少冗余: 不再需要 .and() 来进行配置链式调用。
一致性: Lambda DSL 使得 Spring Security 的配置风格与其他 Spring 项目(如 Spring Integration、Spring Cloud Gateway)保持一致。
简化自定义 DSL: 自定义 DSL 配置变得更加直观,避免了链式调用的复杂性。
Spring Security 7 引入的 Lambda DSL 极大地简化了配置过程,提升了代码的可读性和一致性。从 Spring Security 6.2 开始,这种配置方式已经成为标准,并且将成为未来的推荐方式。对于新项目,建议立即采用这种方式配置 Spring Security。