Spring Security 6迁移问题:JSESSIONID Cookie认证失败返回401
Spring Boot 2.7 升级到 3.0(Spring Security 6.x)的认证问题排查
问题描述
将Spring Boot从2.7版本升级到3.0版本,对应Spring Security升级至6.x版本。修改了SecurityFilterChain配置,并将@EnableGlobalMethodSecurity替换为@EnableMethodSecurity后出现以下问题:
- 携带
Authorization请求头的请求可正常通过认证,且服务端会设置JSESSIONIDCookie - 仅携带
JSESSIONIDCookie而不携带Authorization头的请求返回401未授权,日志显示请求被识别为匿名用户
旧配置(Spring Boot 2.7 + Spring Security 5.x)
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf().disable() .cors().and() .authorizeRequests().anyRequest().authenticated().and() .httpBasic().authenticationEntryPoint(authenticationEntryPoint()).and() .logout().logoutSuccessHandler(logoutSuccessHandler()); return http.build(); } private AuthenticationEntryPoint authenticationEntryPoint() { return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED); } private LogoutSuccessHandler logoutSuccessHandler() { return new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK); }
新配置(Spring Boot 3.0 + Spring Security 6.x)
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(withDefaults()) .authorizeHttpRequests(authorizeHttpRequestsConfigurer -> authorizeHttpRequestsConfigurer./*requestMatchers(GET, SWAGGER_WHITELIST).permitAll().*/anyRequest().authenticated()) .httpBasic(httpBasicConfigurer -> httpBasicConfigurer.authenticationEntryPoint(authenticationEntryPoint())) .logout(logoutConfigurer -> logoutConfigurer.logoutSuccessHandler(logoutSuccessHandler())); return http.build(); }
注解修改
原注解
@EnableWebSecurity @EnableGlobalMethodSecurity(securedEnabled = true)
修改后注解
@EnableWebSecurity @EnableMethodSecurity(securedEnabled = true)
日志信息
正常请求日志(携带Authorization头)
18:36:00.823 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - Securing GET /users/current 18:36:00.924 [http-nio-8080-exec-6] DEBUG o.s.s.w.a.w.BasicAuthenticationFilter - Set SecurityContextHolder to UsernamePasswordAuthenticationToken [Principal=com.example.UserDetailsAdapter@e98a174, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[ROLE_ADMIN]] 18:36:00.925 [http-nio-8080-exec-6] DEBUG o.s.security.web.FilterChainProxy - Secured GET /users/current
异常请求日志(仅携带JSESSIONID Cookie)
18:36:00.948 [http-nio-8080-exec-7] DEBUG o.s.security.web.FilterChainProxy - Securing GET /users/current 18:36:00.949 [http-nio-8080-exec-7] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
问题原因与解决方案
核心原因
Spring Security 6.x默认关闭了HttpSession的安全上下文持久化功能,而Spring Security 5.x(对应Spring Boot 2.7)中默认是开启的。
在Spring Security 5.x中,用户通过HttpBasic认证成功后,认证信息会自动保存到HttpSession中,后续请求携带JSESSIONID Cookie时,系统会从Session中读取认证信息完成授权。但Spring Security 6.x调整了默认行为:SecurityContextHolderFilter默认不再从HttpSession加载安全上下文,也不会将认证后的上下文保存到Session中,导致仅携带Cookie的请求无法获取已认证的用户信息,被识别为匿名用户。
解决方案
在SecurityFilterChain配置中显式开启安全上下文的Session持久化:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(withDefaults()) .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) // 开启安全上下文的Session持久化 .securityContext(securityContext -> securityContext .securityContextRepository(new HttpSessionSecurityContextRepository()) .requireExplicitSave(false) // 自动将认证后的上下文保存到Session ) .httpBasic(basic -> basic.authenticationEntryPoint(authenticationEntryPoint())) .logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler())); return http.build(); }
验证说明
添加配置后:
- 用户首次携带
Authorization头认证成功时,HttpSessionSecurityContextRepository会将认证后的SecurityContext保存到HttpSession,同时返回JSESSIONIDCookie - 后续请求携带
JSESSIONIDCookie时,SecurityContextHolderFilter会从Session中加载已认证的上下文,无需再次携带Authorization头即可完成授权
同时你会发现正常请求的日志中,SessionId不再是null,而是对应的JSESSIONID值。
内容的提问来源于stack exchange,提问作者italktothewind
相关产品推荐
相关产品推荐

