You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3.3中HttpSessionSecurityContextRepository认证后返回403问题

Spring Boot 3.3 服务端会话登录后受保护路由返回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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 10:44:59