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

升级至Spring Security 6后请求间身份认证会话丢失问题排查

升级Spring Security 6后会话丢失问题排查

升级到Spring Security 6后,出现严重的会话丢失问题:基于表单的登录已成功完成(返回HTTP 200及JSESSIONID响应Cookie),但后续请求受保护端点/api/account获取用户信息时,会话完全丢失,无法找到Authentication信息。

日志显示,登录成功后FilterChain已将包含认证信息的SecurityContext正常存入HttpSessionSecurityContextRepository。已知Spring Security 6对BasicAuth有变更,但表单登录无需手动保存会话,这与日志表现一致。

我通过Spring Boot测试复现了该问题,调试发现DefaultSecurityFilterChain的首个过滤器DisableEncodeUrlFilter执行时会话已不存在。以下是相关配置代码、日志及测试代码,请问我遗漏了什么?会话为何会消失?


相关代码

SecurityFilterChain配置

public SecurityFilterChain formLoginSecurityFilterChain(HttpSecurity http, RestAuthenticationEntryPoint entryPoint, RestAuthenticationSuccessHandler successHandler, RestAuthenticationLogoutSuccessHandler logoutSuccessHandler, SimpleUrlAuthenticationFailureHandler failureHandler) throws Exception {
    // @formatter:off
    http.csrf(AbstractHttpConfigurer::disable)
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
            .exceptionHandling(exception -> exception.authenticationEntryPoint(entryPoint))
            .authorizeHttpRequests(authz -> authz
                    .requestMatchers(antMatcher("/"), antMatcher("/index.html")).permitAll()
                    .requestMatchers(antMatcher("/assets/**")).permitAll()
                    .requestMatchers(antMatcher("/api/system-info/**"),antMatcher("/api/system-messages/public")).permitAll()
                    .requestMatchers(antMatcher("/api/admin/system-messages/**")).hasAuthority(Privileges.IDM_ICDP_SYSTEM_ADMIN)
                    .requestMatchers(antMatcher("/api/admin/applied-scripts")).hasAuthority(Privileges.IDM_ICDP_SYSTEM_ADMIN)
                    .requestMatchers(antMatcher("/api/admin/**")).hasAuthority(Privileges.IDM_ICDP_ADMIN)
                    .requestMatchers(antMatcher("/api/viewer/**")).hasAnyAuthority(Privileges.IDM_ICDP_ADMIN, Privileges.IDM_ICDP_VIEWER)
                    .requestMatchers(antMatcher("/api/case-decision/search")).hasAnyAuthority(Privileges.IDM_ICDP_ADMIN, Privileges.IDM_ICDP_VIEWER)
                    .requestMatchers(antMatcher("/api/**")).authenticated()
                    .anyRequest().authenticated()
            )
            .formLogin(form -> form
                    .loginProcessingUrl("/api/login")
                    .successHandler(successHandler)
                    .failureHandler(failureHandler)
            )
            .logout(logout -> logout
                    .logoutUrl("/api/logout")
                    .logoutSuccessHandler(logoutSuccessHandler)
    );
    // @formatter:on
    return http.build();
}

RestAuthenticationSuccessHandler代码

public class RestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    private final int sessionTimeout;


    public RestAuthenticationSuccessHandler(int sessionTimeout) {
        this.sessionTimeout = sessionTimeout;

    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        request.getSession().setMaxInactiveInterval(sessionTimeout);
        clearAuthenticationAttributes(request);
    }

}

Spring Boot测试代码

@Test
void testLoginAndGetAccount() {
    // Mock the authenticateUser method
    HelixUser mockUser = new HelixUser();
    mockUser.setId("admin");
    mockUser.setFirstName("Admin");
    mockUser.setLastName("User");
    List<String> privileges = new ArrayList<>(List.of(Privileges.ACCESS_REST_API, Privileges.IDM_ICDP_ADMIN));
    mockUser.setPrivileges(privileges);
    List<HelixGroup> groups = new ArrayList<>();
    // Configure the mock to return the mockUser when authenticateUser is called
    when(remoteIdmService.authenticateUser("admin", "admin")).thenReturn(mockUser);

    User mockFlowableUser = Mockito.mock(User.class);

    when(userService.getUser("admin")).thenReturn(mockFlowableUser);


    // Form data
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("username", "admin");
    map.add("password", "admin");

    // Headers
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

    // Request entity
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

    // Construct URL
    String url = "http://localhost:" + port + "/icdp/api/login";

    // Make the POST request
    ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

    // Assertions
    assertThat(response.getStatusCode().is2xxSuccessful()).isTrue();
    
    // Assuming the login was successful, now attempt to access /icdp/api/account
    String accountUrl = "http://localhost:" + port + "/icdp/api/account";
    ResponseEntity<String> accountResponse = restTemplate.getForEntity(accountUrl, String.class);

    // Assertions on the account response, FAILS because there is no HttpSession
    assertThat(accountResponse.getStatusCode().is2xxSuccessful()).isTrue();
}

关键日志片段

15:15:02.767 [http-nio-auto-1-exec-1] DEBUG o.s.security.web.context.HttpSessionSecurityContextRepository - Stored SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=admin, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[access-rest-api, access-icdp-admin]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@20222019]
...
15:15:02.835 [http-nio-auto-1-exec-2] TRACE o.s.security.web.context.HttpSessionSecurityContextRepository - No HttpSession currently exists

内容的提问来源于stack exchange,提问作者billerby

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:32:03