Spring Boot 3.3中HttpSessionSecurityContextRepository认证后返回403问题
问题背景
学习Spring Boot 3.3版本,实现基于服务端有状态会话的登录功能,适配JavaScript/React前端。需求是通过浏览器发送JSON完成登录,后续使用Cookie维持会话。目前认证流程可正常执行,但登录成功后访问所有受保护路由均返回403错误,使用Postman测试。
手动在Rest Controller中实现认证逻辑,可接收JSON格式的用户名和密码,认证环节正常,但安全上下文未正确存入会话,调试发现HttpSessionRepository中的securityContext始终为null。
核心代码
主安全配置类
@Configuration @EnableWebSecurity(debug = true) @EnableMethodSecurity public class SecurityConfig { @Autowired private MuserDetailsService muserDetailsService; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(customizer -> { customizer .requestMatchers("/", "/public" , "/login", "/all-methods").permitAll() .requestMatchers(HttpMethod.GET, "/csrf/latest").permitAll(); }); return http.build(); } @Bean public AuthenticationManager getAuthenticationManager() throws Exception { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(muserDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return new ProviderManager(authProvider); } @Bean public RoleHierarchy roleHierarchy() { RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); roleHierarchy.setHierarchy("ROLE_MYTHICAL_USER > ROLE_GRANDPARENT > ROLE_PARENT > ROLE_CHILD"); return roleHierarchy; } @Bean public MethodSecurityExpressionHandler getMethodSecurityExpressionHandler() { DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); expressionHandler.setRoleHierarchy(roleHierarchy()); return expressionHandler; } @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } }
会话Bean配置
@Configuration public class SessionBeanConfig { @Bean public HttpSessionSecurityContextRepository getSecurityContextRepository() { return new HttpSessionSecurityContextRepository(); } @Bean public SecurityContextHolderStrategy getSecurityContextHolderStrategy() { return SecurityContextHolder.getContextHolderStrategy(); } }
登录接口
@RestController public class LoginApi { @Autowired private HttpSessionSecurityContextRepository repo; @Autowired private SecurityContextHolderStrategy securityContextHolderStrategy; @Autowired private AuthenticationManager authenticationManager; @PostMapping("/login") public LoginResponse loginPost( @RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response ) { Authentication authentication = new UsernamePasswordAuthenticationToken( loginRequest.username(), loginRequest.password() ); Authentication authenticated = authenticationManager.authenticate(authentication); SecurityContext context = securityContextHolderStrategy.createEmptyContext(); context.setAuthentication(authenticated); securityContextHolderStrategy.setContext(context); repo.saveContext(context, request, response); return new LoginResponse( loginRequest.username(), loginRequest.password(), true, "Login successful!", authenticated, null ); } }
调试结果
登录请求可正常返回成功响应,但访问/private等受保护接口时返回403,日志显示请求携带JSESSIONID,但HttpSessionRepository中securityContext为null。
解决建议
1. 关联SecurityContextRepository到SecurityFilterChain
当前SecurityConfig未将HttpSessionSecurityContextRepository配置到HttpSecurity中,导致Spring Security无法从会话中读取安全上下文。修改securityFilterChain方法:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, HttpSessionSecurityContextRepository securityContextRepository) throws Exception { http.authorizeHttpRequests(customizer -> { customizer .requestMatchers("/", "/public" , "/login", "/all-methods").permitAll() .requestMatchers(HttpMethod.GET, "/csrf/latest").permitAll() .anyRequest().authenticated(); // 明确所有其他路由需要认证 }) .securityContext(context -> context .securityContextRepository(securityContextRepository) // 配置会话上下文仓库 ); return http.build(); }
2. 确保登录时会话已创建
调用repo.saveContext前,强制创建HttpSession,避免因会话未初始化导致上下文无法存储:
@PostMapping("/login") public LoginResponse loginPost( @RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response ) { // ... 认证逻辑 request.getSession(true); // 强制创建会话 repo.saveContext(context, request, response); // ... 返回响应 }
3. 临时禁用CSRF(测试阶段)
Postman测试时若未携带CSRF Token,Spring Security默认的CSRF保护会拦截请求,可临时禁用排查问题:
http.csrf(csrf -> csrf.disable());
生产环境需配置CSRF Token传递:前端从
/csrf接口获取Token,在请求头中携带X-XSRF-TOKEN。
4. 检查认证对象的权限信息
确认authenticated对象包含正确的权限(ROLE_前缀),若权限缺失,也会导致403。可在登录接口中打印authenticated.getAuthorities()验证。
内容的提问来源于stack exchange,提问作者theMyth

