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

React与Spring Boot Security交互遇403错误及XSRF-TOKEN未定义问题

问题描述

我搭建了React前端+Java/Spring Boot后端的应用,未添加Spring-Security依赖前REST请求可正常运行。现在需要实现用户注册/登录功能,引入Spring Security后遇到以下问题:

  • 请求返回403错误
  • 前端获取的X-XSRF-TOKEN请求头为undefined

最终目标是通过React登录页调用后端完成账号密码验证,基于Cookie创建用户会话,后续将网站部署至AWS,希望得到适配该场景的配置建议。

当前配置代码

SecurityConfig.java

package com.allyoucanexercise.back_end.helpers;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;

import java.util.List;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .cors(cors -> cors.configurationSource(request -> {
                    CorsConfiguration config = new CorsConfiguration();
                    config.setAllowCredentials(true);
                    config.setAllowedOrigins(List.of("http://localhost:3000"));
                    config.addAllowedHeader("*");
                    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
                    return config;
                }))
                .csrf(csrf -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/auth/**").permitAll() 
                        .anyRequest().authenticated())
                .formLogin(form -> form.disable()) 
                .httpBasic(basic -> basic.disable());

        return http.build();
    }
}

application.properties

spring.application.name=back-end
spring.sql.init.mode=always
# spring.datasource.initialize=true
server:
  error:
    include-message: always
    include-binding-errors: always

server.servlet.session.cookie.http-only=true
# Enable in production (for HTTPS):::
# server.servlet.session.cookie.secure=true

前端调用代码

const getExercisesByGroup = async () => {
        const csrfToken = Cookies.get('XSRF-TOKEN'); // Read the CSRF token from the cookie
        try {
            const response = await axios.get(`http://localhost:8080/api/exercises/group/${exerciseGroup}`, {
                headers: {
                'X-XSRF-TOKEN': csrfToken,
                },
                withCredentials: true, 
            });
            setExercisesByGroup(response.data);
        } catch (error) {
            console.error("Error fetching exercises:", error);
        }
    };
解决方案与配置建议

1. 解决CSRF Token未生成问题

Spring Security默认不会为未认证的GET请求自动生成CSRF Cookie,导致前端无法获取X-XSRF-TOKEN。你需要:

  • 在/api/auth下添加一个无权限接口,触发CSRF Cookie生成:
    @RestController
    @RequestMapping("/api/auth")
    public class AuthController {
        @GetMapping("/csrf")
        public ResponseEntity<Void> getCsrfToken() {
            // 无需返回内容,后端会自动设置XSRF-TOKEN Cookie
            return ResponseEntity.ok().build();
        }
    }
    
  • 前端在应用初始化时(比如App组件挂载阶段)先调用该接口,确保Cookie被设置:
    useEffect(() => {
        axios.get('http://localhost:8080/api/auth/csrf', { withCredentials: true })
            .catch(err => console.error("Failed to get CSRF token:", err));
    }, []);
    

2. 调整SecurityConfig配置

  • 会话策略优化:将SessionCreationPolicy.ALWAYS改为SessionCreationPolicy.IF_REQUIRED,避免不必要的会话创建:
    .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
    
  • CSRF配置增强:显式指定CSRF请求处理器,适配前端请求逻辑:
    .csrf(csrf -> csrf
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()))
    

3. 前端请求规范调整

  • 确保所有请求(包括登录)都携带withCredentials: true,保证会话Cookie和CSRF Cookie正常传递:
    const login = async (username, password) => {
        const csrfToken = Cookies.get('XSRF-TOKEN');
        try {
            const response = await axios.post('http://localhost:8080/api/auth/login', 
                { username, password },
                {
                    headers: { 'X-XSRF-TOKEN': csrfToken },
                    withCredentials: true
                }
            );
            // 登录成功后,后续请求会自动携带会话Cookie
        } catch (error) {
            console.error("Login failed:", error);
        }
    };
    

4. 实现登录认证核心逻辑

需要添加用户认证相关组件完成账号密码验证:

  • 实现UserDetailsService,从数据库加载用户信息:
    @Service
    public class CustomUserDetailsService implements UserDetailsService {
        @Autowired
        private UserRepository userRepository;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userRepository.findByUsername(username)
                    .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
            return User.withUsername(user.getUsername())
                    .password(user.getPassword()) // 确保密码已通过BCrypt加密存储
                    .roles(user.getRoles().toArray(new String[0]))
                    .build();
        }
    }
    
  • 添加登录接口,使用AuthenticationManager完成认证:
    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody LoginRequest loginRequest, AuthenticationManager authManager) {
        Authentication authentication = authManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return ResponseEntity.ok("Login successful");
    }
    
  • 在SecurityConfig中暴露AuthenticationManager Bean:
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
    

5. AWS部署适配配置

  • Cookie安全配置:生产环境强制开启secure属性,确保Cookie仅通过HTTPS传输:
    server.servlet.session.cookie.secure=true
    server.servlet.session.cookie.same-site=Lax # 根据业务场景可选择Strict
    
  • CORS域名适配:将allowedOrigins替换为前端实际部署域名(如CloudFront分配的域名或S3静态网站域名):
    config.setAllowedOrigins(List.of("https://your-frontend-domain.com"));
    
  • 多实例会话共享:如果后端部署多实例(如ECS集群),使用Redis存储会话避免丢失:
    1. 添加Redis依赖:
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.session</groupId>
          <artifactId>spring-session-data-redis</artifactId>
      </dependency>
      
    2. 配置Redis连接:
      spring.redis.host=your-redis-endpoint.redis.amazonaws.com
      spring.redis.port=6379
      spring.redis.password=your-redis-password
      spring.session.store-type=redis
      
  • HTTPS强制配置:通过AWS ALB(应用负载均衡)或CloudFront配置HTTPS,确保前后端通信全程加密。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 22:02:12