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

ReactTS前端未向SpringBoot后端返回Cookie问题求助

跨域场景下Cookie会话管理问题及Spring Sessions实现方案求助

我使用ReactTS+Vite+Mantine构建前端,SpringBoot作为后端,尝试通过Cookie实现会话管理时,前端无法向后端返回Cookie,同时求跨域环境下Spring Sessions的实现方案。


后端代码

登录接口(生成sessionID)

@PostMapping("/login")
public ResponseEntity<User> loginUser(
        @RequestParam(value = "rmbr", defaultValue = "false") boolean rememberMe,
        @RequestBody User user
) {
    UserDatabase userDatabase = new UserDatabase();

    User returnUser = userDatabase.getUserByUsernameOrEmail(user);

    if (returnUser.getUsername() == null || returnUser.getUsername().isEmpty()) {
        return new ResponseEntity<>(returnUser, HttpStatusCode.valueOf(462));
    }
    if (BCrypt.checkpw(user.getPassword(), returnUser.getPassword())) {
        returnUser.setPassword(null);
        UUID sessionId = UUID.randomUUID();
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.add(HttpHeaders.SET_COOKIE,"SESSIONID=" + userDatabase.createCookie(sessionId.toString(), user, rememberMe));


        return ResponseEntity
                .ok()
                .headers(responseHeaders)
                .body(returnUser);
    }
    return new ResponseEntity<>(new User(), HttpStatusCode.valueOf(463));
}

注:也曾尝试使用HttpServletResponse response方式创建Cookie对象,问题依旧。

获取会话详情接口

@GetMapping("/details")
public ResponseEntity<User> getUserFromSession(
        @CookieValue("SESSIONID") String sessionId
) {
    UserDatabase userDatabase = new UserDatabase();
    User user = userDatabase.getCookieUser(sessionId);
    user.setPassword(null);
    return new ResponseEntity<>(new User(), HttpStatusCode.valueOf(200));
}

调用该接口时,控制台报错:

2023-12-09T11:41:59.960+02:00  WARN 19748 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestCookieException: Required cookie 'SESSIONID' for method parameter type String is not present]

Spring Security CORS配置

@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true); // 允许携带凭证(如Cookie)
    config.setAllowedOriginPatterns(List.of("*")); // 允许所有源
    config.addAllowedHeader("*"); // 允许所有请求头
    config.addAllowedMethod("*"); // 允许所有HTTP方法
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

前端代码

登录函数

const handleLogin = async () => {
        const user: User = {
            email: (/^\S+@\S+$/.test(form.values.email) ? form.values.email : ''),
            username: (/^\S+@\S+$/.test(form.values.email) ? '' : form.values.email),
            password: form.values.password,
            profilePhoto: '',
            bio: '',
            role: 'user',
            orgName: ''
        };
        try {
            const response = await fetch( serverIp + 'user/login', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(user),
            });
            if (response.ok) {
                login(await response.json())
                closeModal()
            } else if (response.status == 462) {
                setErrorMessage('User Does not Exist');
            } else if (response.status == 463) {
                setErrorMessage('Invalid Password');
            }
        } catch (error) {
            setErrorMessage('Could not connect to server, Please check internet connection')
            console.error('login fetch error', error)
        } finally {
            close()
        }

    }

页面加载时获取用户详情逻辑

const fetchUserDetails = async () => {
        try {
            const response = await fetch(`${serverIp}user/details`, {
                method: 'GET',
                credentials: 'include',
            });

            if (response.ok) {
                const user = await response.json();
                login(user); // 根据API响应更新用户信息
            } else {
                console.error('No active sessions found for this device');
            }
        } catch (error) {
            console.error('Error fetching user details:', error);
        }
    };
    useEffect(() => {
        fetchUserDetails();
    }, []);

已尝试操作

  • 开启浏览器所有Cookie权限
  • 分别用Spring Cookie和HttpServletResponse两种方式创建Cookie
  • 功能在Postman中正常,但浏览器中异常
  • 尝试设置HttpOnly为true、false或不设置
  • 调整Cookie的MaxAge为-1或8小时
  • 设置Cookie的Domain和Path属性
  • 测试过localhost、127.0.0.1等不同地址

解决方案

一、修复Cookie不回传问题

  1. 前端登录请求添加credentials: 'include'
    登录接口的fetch请求未携带凭证配置,导致浏览器可能不保存Cookie或后续请求不携带。修改登录函数:
const response = await fetch( serverIp + 'user/login', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify(user),
    credentials: 'include' // 新增此行
});
  1. 规范后端Cookie设置
    避免直接拼接SET-COOKIE头,改用Cookie对象配置完整属性:
@PostMapping("/login")
public ResponseEntity<User> loginUser(
        @RequestParam(value = "rmbr", defaultValue = "false") boolean rememberMe,
        @RequestBody User user,
        HttpServletResponse response // 注入response
) {
    // ... 原有逻辑省略
    if (BCrypt.checkpw(user.getPassword(), returnUser.getPassword())) {
        returnUser.setPassword(null);
        UUID sessionId = UUID.randomUUID();
        // 创建Cookie对象
        Cookie sessionCookie = new Cookie("SESSIONID", sessionId.toString());
        sessionCookie.setHttpOnly(true); // 建议开启,防范XSS攻击
        sessionCookie.setSecure(false); // 本地开发设false,生产HTTPS环境设true
        sessionCookie.setDomain("localhost"); // 与前端域名一致,不要加端口
        sessionCookie.setPath("/"); // 所有路径都可携带该Cookie
        sessionCookie.setMaxAge(rememberMe ? 8 * 3600 : -1); // 记住我则保存8小时,否则会话级Cookie
        response.addCookie(sessionCookie);
        
        return ResponseEntity.ok(returnUser);
    }
    // ... 原有逻辑省略
}
  1. 修正CORS配置
    当allowCredentials设为true时,allowedOriginPatterns不能用*,需指定具体前端源:
@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    // 替换为你的前端实际地址,比如http://localhost:5173
    config.setAllowedOrigins(List.of("http://localhost:5173")); 
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}
  1. 调整Cookie的SameSite属性
    跨域场景下,若前端是POST请求(如登录),需设置SameSite为Lax或None(设None必须搭配Secure: true,即HTTPS环境):
sessionCookie.setSameSite("Lax"); // 或根据场景设None

二、跨域Spring Sessions实现方案

1. 引入依赖(以Redis存储为例)

在pom.xml中添加:

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2. 配置application.yml

spring:
  session:
    store-type: redis # 用Redis存储会话
    timeout: 8h # 会话超时时间
  redis:
    host: localhost # Redis地址
    port: 6379 # Redis端口
server:
  servlet:
    session:
      cookie:
        http-only: true
        secure: false # 生产环境设true
        same-site: Lax # 跨域场景可设None(需HTTPS)
        domain: localhost # 与前端域名一致
        path: /

3. 整合Spring Security(可选)

若使用Spring Security,无需手动生成SESSIONID,框架会自动处理会话:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .cors(Customizer.withDefaults()) // 启用CORS配置
            .csrf(csrf -> csrf.disable()) // 开发环境可临时关闭,生产需配置
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/user/login").permitAll()
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            );
        return http.build();
    }
}

4. 前端配置

确保所有请求都携带credentials: 'include',同之前的修复方案。


内容的提问来源于stack exchange,提问作者Johan Du Toit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 18:12:04