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中暴露
AuthenticationManagerBean:@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存储会话避免丢失:
- 添加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> - 配置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
- 添加Redis依赖:
- HTTPS强制配置:通过AWS ALB(应用负载均衡)或CloudFront配置HTTPS,确保前后端通信全程加密。
内容的提问来源于stack exchange,提问作者Alisha Burgfeld
相关产品推荐
相关产品推荐

