升级Spring Security后SecurityContext为空的认证异常问题
核心问题原因
升级Spring Security后,手动通过SecurityContextHolder.getContext().setAuthentication()设置认证信息仍被重定向到登录页,本质是手动设置的SecurityContext未持久化到HttpSession。Spring Security默认通过HttpSessionSecurityContextRepository将SecurityContext存储到Session中,仅通过ThreadLocal设置的话,请求结束后ThreadLocal会被清理,后续请求无法获取认证信息,因此触发未认证拦截重定向。
另外你的dbAuthentication方法里,UsernamePasswordAuthenticationToken在传入authorities参数时已经是已认证状态,无需再调用isAuthenticated()判断,这个判断是多余的。
修复步骤
1. 修改认证方法,持久化SecurityContext到Session
注入HttpServletRequest和HttpSessionSecurityContextRepository,在设置Authentication后手动将SecurityContext保存到Session:
import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; @Autowired private HttpServletRequest request; @Autowired private HttpSessionSecurityContextRepository securityContextRepository; public boolean dbAuthentication(String username, String password) { try { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (passwordEncoder.matches(password, userDetails.getPassword())) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); // 创建并设置SecurityContext SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(authToken); // 保存到Session,确保后续请求能获取 securityContextRepository.saveContext(securityContext, request, null); // 同步到当前线程的SecurityContextHolder SecurityContextHolder.setContext(securityContext); return true; } } catch(Exception e) { e.printStackTrace(); } return false; }
2. 优化WebSecurityConfig配置
- 移除重复的静态资源忽略配置(
filterChain中已配置permitAll,无需在WebSecurityCustomizer重复设置) - 修正logout路径,避免与登录页路径冲突:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)) .authorizeHttpRequests(a -> a .requestMatchers("/app/**", "/signin.html", "/forgot.html", "/getCustomCss/**", "/oauth2sso/**", "/isLdapEnabled/**", "/isSamlEnabled/**", "/prefetchFiles*", "/languages/getLanguages*", "/getSessionTimeout", "/wakeUpElastic/**", "/forgotPassword*", "/recoverPassword*", "/authenticate*", "/build/**", "/ext/**", "/resources/**", "/logout*") .permitAll() .anyRequest().authenticated() ) .formLogin(formLogin -> formLogin .loginPage("/signin.html") .usernameParameter("username") .passwordParameter("password") .defaultSuccessUrl("/index.html", true) .failureUrl("/signin.html?error=true") ) .logout(l -> l .invalidateHttpSession(true) .deleteCookies("SESSIONID") .logoutUrl("/logout") // 改为独立的logout路径 .logoutSuccessUrl("/signin.html?logout") ); return http.build(); } // 精简WebSecurityCustomizer,移除重复路径 @Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().requestMatchers("/build/**", "/ext/**", "/resources/**"); }
3. 可选:使用Spring Security标准认证流程(更规范)
用AuthenticationManager处理认证,替代手动创建Token的方式:
@Autowired private AuthenticationManager authenticationManager; public boolean dbAuthentication(String username, String password) { try { Authentication authRequest = new UsernamePasswordAuthenticationToken(username, password); Authentication authResult = authenticationManager.authenticate(authRequest); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(authResult); securityContextRepository.saveContext(securityContext, request, null); SecurityContextHolder.setContext(securityContext); return true; } catch (AuthenticationException e) { e.printStackTrace(); return false; } }
同时在WebSecurityConfig中暴露AuthenticationManager:
@Bean public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { return http.getSharedObject(AuthenticationManagerBuilder.class) .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()) .and() .build(); }
关键说明
Spring Security新版本对SecurityContext的管理更严格,手动设置认证信息后必须显式保存到Session,否则后续请求无法识别已认证状态,这是导致重定向到登录页的核心原因。
内容的提问来源于stack exchange,提问作者ionut logofatu

