Spring Security+React登录认证异常:错误密码仍跳转至首页
Spring Security + React 登录认证异常:错误密码仍跳转至首页
即便输入错误密码,前端仍跳转至/home页面,Chrome控制台显示请求状态为200。但通过Postman或直接访问localhost:8080/login时认证正常,仅React端(localhost:5173/login)存在该问题。
Spring Security 后端代码
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public UserDetailsManager userDetailsManager(DataSource dataSource){ JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource); jdbcUserDetailsManager.setUsersByUsernameQuery( "select user_id,pw,active from members where user_id=?" ); jdbcUserDetailsManager.setAuthoritiesByUsernameQuery( "select user_id,role from roles where user_id=?" ); return jdbcUserDetailsManager; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http .authorizeHttpRequests(configurer -> { configurer .requestMatchers(HttpMethod.POST,"/login").permitAll() .requestMatchers(HttpMethod.GET,"api/employees").hasRole("EMPLOYEE") .requestMatchers(HttpMethod.GET,"api/employees/**").hasRole("EMPLOYEE") .requestMatchers(HttpMethod.POST,"api/employees").hasRole("MANAGER") .requestMatchers(HttpMethod.PUT,"api/employees/**").hasRole("MANAGER") .requestMatchers(HttpMethod.DELETE,"api/employees").hasRole("ADMIN") .anyRequest().authenticated(); }) .formLogin(form -> { form .loginProcessingUrl("/login") .defaultSuccessUrl("/home", true) .failureForwardUrl("/") .permitAll(); }) .logout(logout -> { logout .logoutSuccessUrl("/login") .permitAll(); }) ; //use Http basic authentication http.httpBasic(Customizer.withDefaults()); //disable cross site request forgery(CSFR) //not required for stateless rest api that use get,post,put,patch,delete http.csrf(csrf -> csrf.disable()); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
React 前端登录页面代码
import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; import { Grid, TextField, Button, Typography } from '@mui/material'; const Login = () => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const navigate = useNavigate(); const handleLogin = async () => { try { const response = await axios.post('http://localhost:8080/login', new URLSearchParams({ username: username, password: password }), { withCredentials: true }); console.log(response); if (response.status === 200) { navigate('/home'); } else { console.error('Login failed'); } } catch (error) { console.error('Login failed:', error.message); } }; return ( <Grid container spacing={2} justifyContent="center" alignItems="center" style={{ minHeight: '100vh' }}> <Grid item xs={12} sm={6}> <Typography component="h1" variant="h5" align="center"> Sign in </Typography> <form> <Grid container spacing={2} justifyContent="center" alignItems="center"> <Grid item xs={12}> <TextField variant="outlined" margin="normal" required fullWidth id="username" label="Username" name="username" autoComplete="username" autoFocus value={username} onChange={(e) => setUsername(e.target.value)} /> </Grid> <Grid item xs={12}> <TextField variant="outlined" margin="normal" required fullWidth name="password" label="Password" type="password" id="password" autoComplete="current-password" value={password} onChange={(e) => setPassword(e.target.value)} /> </Grid> <Grid item xs={12}> <Button type="button" fullWidth variant="contained" color="primary" onClick={handleLogin} > Sign In </Button> </Grid> </Grid> </form> </Grid> </Grid> ); }; export default Login;
问题原因
- 后端formLogin逻辑不适配API请求:当前配置的
failureForwardUrl("/")是针对服务器端渲染的页面跳转,登录失败时Spring Security会返回200状态码并转发到首页内容,而非返回401等错误状态码,导致前端误判请求成功。 - 前端仅依赖状态码判断结果:React代码只要检测到响应状态码为200就跳转,忽略了登录失败时后端返回的实际内容。
解决方案
后端调整(适配API响应)
修改SecurityConfig中的formLogin配置,手动处理成功/失败的响应,返回明确的状态码和JSON数据:
.formLogin(form -> { form .loginProcessingUrl("/login") .successHandler((request, response, authentication) -> { // 登录成功返回200和成功标识 response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/json"); response.getWriter().write("{\"success\": true}"); response.getWriter().flush(); }) .failureHandler((request, response, exception) -> { // 登录失败返回401和错误信息 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); response.getWriter().write("{\"success\": false, \"message\": \"用户名或密码错误\"}"); response.getWriter().flush(); }) .permitAll(); })
同时移除defaultSuccessUrl和failureForwardUrl配置。
前端调整(正确解析响应)
修改handleLogin函数,根据后端返回的success字段判断登录结果,而非仅依赖状态码:
const handleLogin = async () => { try { const response = await axios.post('http://localhost:8080/login', new URLSearchParams({ username: username, password: password }), { withCredentials: true }); if (response.data.success) { navigate('/home'); } else { console.error('Login failed:', response.data.message); // 可在此添加用户可见的错误提示,比如弹窗或文字提示 } } catch (error) { console.error('Login failed:', error.response?.data?.message || error.message); } };
补充:配置CORS(避免跨域问题)
在Spring Boot中添加CORS配置,允许React域名访问:
@Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://localhost:5173"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }
内容的提问来源于stack exchange,提问作者Prashant Kumar
相关产品推荐
相关产品推荐

