升级至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
相关产品推荐
相关产品推荐

