React-Spring Boot项目/api/quiz/random异常重定向至/login问题排查
Auth Controller已成功设置登录用户会话(日志显示“User session set for: willow”),前端受保护路由正常工作,但调用/api/quiz/random接口时,请求始终被重定向至http://localhost:8080/login,触发404错误(错误信息:GET http://localhost:8080/login 404 (Not Found),对应Quizpage.js第48行的axios请求)。
前端请求代码(Quizpage.js)
const fetchQuizItem = async () => { try { const response = await axios.get('http://localhost:8080/api/quiz/random', { withCredentials: true, // Ensures cookies are sent with the request }); console.log('Quiz Item:', response.data); setQuizItem(response.data); } catch (err) { console.error('Failed to load quiz item', err); setError('Failed to load quiz item'); } };
后端QuizController代码
package quiz.Controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import quiz.Entity.QuizItem; import quiz.Entity.User; import quiz.Entity.UserQuizAttempt; import quiz.Service.QuizItemService; import quiz.Service.UserQuizAttemptService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; @RestController @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true", allowedHeaders = "*") @RequestMapping("/api/quiz") public class QuizController { private static final Logger logger = LoggerFactory.getLogger(QuizController.class); @Autowired private QuizItemService quizService; @Autowired private UserQuizAttemptService userQuizAttemptService; @PostMapping("/submit") public boolean submitQuiz(@RequestParam int guessedYear, @RequestParam Long quizItemId, HttpServletRequest request) { User user = (User) request.getSession().getAttribute("user"); logger.info("User object: {}", user); if (user == null) { logger.error("User not logged in"); throw new IllegalArgumentException("User not logged in"); } QuizItem quizItem = quizService.getQuizItemById(quizItemId); UserQuizAttempt attempt = userQuizAttemptService.saveUserQuizAttempt(user, quizItem, guessedYear); return attempt.getPoints() > 0; } @GetMapping("/random") public QuizItem getRandomQuizItem(HttpServletRequest request) { User user = (User) request.getSession().getAttribute("user"); logger.info("User object: {}", user); if (user == null) { logger.error("User not logged in"); throw new IllegalArgumentException("User not logged in"); } return quizService.getDailyQuizItem(); } }
已排查信息
- 所有前端JS页面无主动重定向至/login的逻辑
- 用户会话已成功创建(Auth Controller日志确认)
- 前端受保护路由可正常识别登录状态
可能的原因及解决方案
1. Spring Security 触发默认登录重定向
Spring Security默认拦截所有请求,若未将/api/quiz/**配置正确的认证规则,会自动重定向到默认/login端点(项目未配置该端点导致404)。
解决步骤:
修改SecurityConfig,禁用默认重定向,改为返回401错误:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() // 放行登录注册接口 .antMatchers("/api/quiz/**").authenticated() // 要求认证 .anyRequest().authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { // 未认证时返回401,而非重定向 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); }); } }
2. 会话Cookie跨域配置缺失
尽管前端设置了withCredentials: true,但后端Cookie的SameSite、Domain等属性配置错误,会导致Cookie未被正确携带到请求中,后端判定用户未登录。
检查application.properties:
server.servlet.session.cookie.same-site=Lax # 跨域场景可改为None server.servlet.session.cookie.domain=localhost server.servlet.session.cookie.http-only=true
3. 手动会话验证与Spring Security冲突
手动从HttpServletRequest获取会话用户的逻辑,可能与Spring Security的会话管理机制冲突,导致Security认为用户未认证。
优化方案:
改用Spring Security提供的@AuthenticationPrincipal注解获取用户:
@GetMapping("/random") public QuizItem getRandomQuizItem(@AuthenticationPrincipal User user) { logger.info("User object: {}", user); if (user == null) { logger.error("User not logged in"); throw new IllegalArgumentException("User not logged in"); } return quizService.getDailyQuizItem(); }
需确保User类实现UserDetails接口,或在Security配置中正确配置用户认证逻辑。
4. Spring Security层面跨域配置缺失
仅在Controller添加@CrossOrigin可能不够,需在Security中补充全局跨域配置:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://localhost:3000"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
同时在SecurityConfig中启用CORS:
http.cors().and()...
内容的提问来源于stack exchange,提问作者Laura Ferry

