SpringBoot升级到3.4.3,对应的Spring-Security也由5.x升级到6.x,在升级的过程中,坑是一踩一个准,变化非常大,现在就来整理一下这些变化。
一、被废除的类、注解和替代品
1、移除WebSecurityConfigurerAdapter
在Spring Security 5.x中,常用继承WebSecurityConfigurerAdapter类来进行安全配置。
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin(); }}
而在Spring Security 6.x中,推荐使用SecurityFilterChain和@EnableWebSecurity进行配置,移除了对WebSecurityConfigurerAdapter的依赖。
@Configuration@EnableWebSecuritypublic class SecurityConfig {
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin(); return http.build(); }}
变化总结:WebSecurityConfigurerAdapter被移除,推荐使用SecurityFilterChain来配置HTTP安全设置。
2、AuthenticationManager的bean获取
在Spring Security 5.x中,AuthenticationManager的bean获取是通过继承 WebSecurityConfigurerAdapter 类,通过该类的authenticationManager() 获取bean。
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .... // 增加登录拦截 .and().addFilter(new JWTLoginFilter(authenticationManager())) // 增加是否登录过滤 .addFilter(new JWTAuthenticationFilter(authenticationManager())) ... }}
而在Spring Security 6.x中,WebSecurityConfigurerAdapter 被移除,推荐使用AuthenticationConfiguration 获取AuthenticationManager的bean。配置变得更加简洁,并且符合现代Spring风格。
@Configuration@EnableWebSecuritypublic class SecurityConfig {
// 正确方式:通过AuthenticationConfiguration获取 @Bean AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); }}
变化总结:WebSecurityConfigurerAdapter被移除,转而通过AuthenticationConfiguration 来获取AuthenticationManager 的bean配置过来验证管理。
3、移除@EnableGlobalMethodSecurity
在Spring Security 5.x中,启用方法级安全控制使用@EnableGlobalMethodSecurity注解。
@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig { // 允许使用@PreAuthorize等注解}
而在Spring Security 6.x中,@EnableGlobalMethodSecurity被新的注解@EnableMethodSecurity替代。这个新的注解提供了更细粒度的控制,简化了配置。
@Configuration@EnableMethodSecurity // 新的注解,用于开启方法级安全public class SecurityConfig { // 允许使用@PreAuthorize等注解}
变化总结:@EnableGlobalMethodSecurity变成了@EnableMethodSecurity,新的注解会自动启用方法级别的安全,并且更加符合Spring 6.x的配置风格。
4、HttpSecurity的API变更
在Spring Security 5.x中,HttpSecurity配置较为常见的方式是使用http.authorizeRequests()来配置URL级别的权限控制。
http .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin();
而在Spring Security 6.x中,authorizeRequests()方法已被替换为authorizeHttpRequests(),这是对方法命名的优化,旨在使API更加直观。
http .authorizeHttpRequests() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin();
不止这一个方法,还有其他方法,这里就不举例了。
变化总结:authorizeRequests()方法被authorizeHttpRequests()替代,主要是API方法的重命名,使得方法名称更加一致和简洁。
5、PermissionEvaluator实现类的配置
在使用Spring thymeleaf 整合 Spring Security 5.x 时,我需要在方法上加入自定义的授权,比如:
@PreAuthorize("hasPermission('goods', 'read') or hasRole('ROLE_ADMINISTRATOR')")
这个注解的意思是,只要具备 goods:read 或 ROLE_ADMINISTRATOR 权限的用户都可以访问这个方法。
在版本5.x,只要实现了PermissionEvaluator 接口,并注册为组件,就能实现这个功能。
@Component("permissionEvaluator")public class MyPermissionEvaluator implements PermissionEvaluator { @Override public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { // 匿名访问时,无权限 if (authentication.getPrincipal().toString().equalsIgnoreCase("anonymousUser")) { return false; } String privilege = targetDomainObject + ":" + permission; return authentication.getAuthorities().stream().anyMatch(authority -> privilege.equalsIgnoreCase(authority.getAuthority())); }
/** * 总是认为有权限返回true,否则返回false */ @Override public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) { return false; }}
但在6.x中,我还需要在配置文件中配置一个bean才能实现同样的功能:
@BeanMethodSecurityExpressionHandler methodSecurityExpressionHandler(MyPermissionEvaluator permissionEvaluator) { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); expressionHandler.setPermissionEvaluator(permissionEvaluator); return expressionHandler;}
也就是说我们需要设置 ExpressionHandler 来关联这个Evaluator。不设置这个bean的话,框架一升级,所有的方法都没有权限访问,太尴尬了。
二、比较恶心的变化
1、退出的变化
在整合 jjwt 时,Spring Security 5.x 退出默认Get方法。
http .... // 退出登录的处理 .and().logout().addLogoutHandler(myLogoutHandler)
而在Spring Security 6.x中不一样,退出默认使用POST方式,GET方式需要自己调整。
http .... // 退出登录的处理 .logout(logout->logout // 允许GET方式退出 .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")) .addLogoutHandler(myLogoutHandler) // 清除安全上下文 .addLogoutHandler((request, response, authentication) -> { SecurityContextHolder.clearContext(); }) .clearAuthentication(true) )
这样也好,退出使用什么方法可以自定义。
需要注意的是,整合 jjwt 是前后端状态分离的架构设计。如果使用表单退出,默认的还是get方式。
2、登录调整的变化
在使用Spring thymeleaf 整合 Spring Security 5.x 时,登录后会以 get 方式直接跳往指定页面。
// 配置登录页面、登录成功跳转页面、登录失败跳转页面http.formLogin() .loginPage("/login") .successForwardUrl("/admin/index") .failureUrl("/login?error=true") .permitAll();
而在Spring Security 6.x中不一样,登录后的跳转方法默认使用POST方式,GET方式需要自己调整。
http // 表单登录配置 .formLogin(form -> form .usernameParameter("username") .passwordParameter("password") .loginPage("/login") // .successForwardUrl("/admin/index") // 直接跳转页面,而且还是post方法 .defaultSuccessUrl("/admin/index", true) // 强制重定向,使用get方式,如果有历史页面也不会回到历史页面 // .successHandler((request, response, authentication) -> { // response.sendRedirect("/admin/index"); // 明确使用GET重定向 // }) .failureUrl("/login?error=true") .permitAll() )
successForwardUrl()方法默认POST方法,如果需要使用get方式跳转,则需要使用 defaultSuccessUrl() 或 successHandler() 自定义get方式。
三、看似没变实则变化的
1、@EnableWebSecurity注解
在Spring Security 5.x中,它长这样:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)@Target(value = { java.lang.annotation.ElementType.TYPE })@Documented@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class })@EnableGlobalAuthentication@Configurationpublic @interface EnableWebSecurity {
/** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false;}
在Spring Security 6.x中,它长这样:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class, ObservationImportSelector.class })@EnableGlobalAuthenticationpublic @interface EnableWebSecurity {
/** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false;}
在新版本中,增加了HttpSecurityConfiguration.class, ObservationImportSelector.class两个类的导入。
以上这些都是根据我的实战经验进行整理的,可能还有其他变化,没有用到的用到了再说吧。
更多变化请看官网:https://docs.spring.io/spring-security/reference/index.html