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

Spring Boot 2升级至3后认证接口未返回Set-Cookie引发401问题

问题描述

将Angular+Spring Boot 2.x.x应用升级至Spring Boot 3.x.x后,通过Basic Auth头发起的用户端点REST请求认证成功返回200状态码,但响应中缺少Set-Cookie头。由于未设置会话Cookie,后续所有受保护的REST请求均返回401状态码。

在Spring Boot 2.x.x版本中,该请求会正常返回Set-Cookie,此实现方案升级前一直正常工作。

以下是认证请求的请求/响应头:

===> 请求头

GET /api/users/current HTTP/1.1
Host: retrospect.wspfeiffer.com:8443
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/110.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
authorization: Basic XXXXXXXXXXXXXXXXXXXX
Access-Control-Allow-Origin: http://localhost:4200
X-Requested-With: XMLHttpRequest
Connection: keep-alive
Referer: https://retrospect.wspfeiffer.com:8443/login
Cookie: SESSION=N2IzZmU4MDEtZjUwMC00MGU1LTllNDItMzVjODkzZWY1NGE2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Pragma: no-cache
Cache-Control: no-cache

===> 响应头

HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Cache-Control: no-cache
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
Content-Type: application/json
Transfer-Encoding: chunked
Date: Wed, 08 Mar 2023 14:48:29 GMT
Keep-Alive: timeout=60
Connection: keep-alive

安全配置代码如下:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.HashMap;
import java.util.Map;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity(debug = true)
@EnableMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
                        .requestMatchers("/open/api/**").permitAll()
                        .requestMatchers("/index.html").permitAll()
                        .requestMatchers(HttpMethod.GET,
                                         "",
                                         "/",
                                         "/*.html",
                                         "/favicon.ico",
                                         "/*.css",
                                         "/*.js",
                                         "/*.map",
                                         "/assets/**",
                                         "/error").permitAll()
                        .requestMatchers("/api/**").authenticated()
                        .anyRequest().authenticated())
                .httpBasic()
                .and()
                .cors()
                .and()
                .csrf()
                    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and()
                .logout()
                .logoutUrl("/logout");
        http.headers().cacheControl().disable();
        http.headers().frameOptions().disable();
        return http.build();
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:4200");
        config.addAllowedHeader("*");
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("DELETE");
        config.addExposedHeader("Content-Type");
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }

    @Bean
    public PasswordEncoder passwordEncoder()
    {
        String idForEncode = "bcrypt";
        Map<String,PasswordEncoder> encoders = new HashMap<>();
        encoders.put(idForEncode, new BCryptPasswordEncoder());
        encoders.put("sha256", new StandardPasswordEncoder());

        return new DelegatingPasswordEncoder(idForEncode, encoders);
    }
}
解决方案

问题根源在于Spring Boot 3.x.x对应的Spring Security 6.x版本中,HttpBasic认证默认不再主动创建会话,而Spring Boot 2.x.x中的Spring Security 5.x默认会创建会话。要恢复原有行为,需显式配置会话创建策略。

修改安全配置

在filterChain方法中添加会话管理配置,指定会话创建策略为IF_REQUIRED(和Spring Boot 2.x默认行为一致):

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers(HttpMethod.OPTIONS).permitAll()
                    .requestMatchers("/open/api/**").permitAll()
                    .requestMatchers("/index.html").permitAll()
                    .requestMatchers(HttpMethod.GET,
                                     "",
                                     "/",
                                     "/*.html",
                                     "/favicon.ico",
                                     "/*.css",
                                     "/*.js",
                                     "/*.map",
                                     "/assets/**",
                                     "/error").permitAll()
                    .requestMatchers("/api/**").authenticated()
                    .anyRequest().authenticated())
            .httpBasic()
            .and()
            // 新增会话管理配置
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            )
            .cors()
            .and()
            .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .and()
            .logout()
            .logoutUrl("/logout");
    http.headers().cacheControl().disable();
    http.headers().frameOptions().disable();
    return http.build();
}

同时需要导入SessionCreationPolicy类:

import org.springframework.security.config.http.SessionCreationPolicy;

补充说明

  • SessionCreationPolicy.IF_REQUIRED:仅在需要时创建会话,和Spring Boot 2.x默认逻辑一致
  • 如果需要强制每次认证都创建新会话,可替换为SessionCreationPolicy.ALWAYS
  • 需确保前端请求配置了withCredentials: true(Angular中HttpClient请求时设置),否则Cookie无法被正确保存和发送

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:25:02