
一、问题背景
前后端分离架构下,React前端通常运行在3000端口,Spring Security后端运行在8080端口,端口不同属于跨域场景。当React发起登录POST请求时,浏览器会先发送OPTIONS预检请求,如果后端没有正确配置跨域规则,或者Spring Security的安全拦截规则没有放行登录接口,就会导致登录请求失败。同时POST请求的参数格式、登录成功后的状态保持也是需要重点处理的部分。
二、Spring Security后端配置
2.1 跨域配置类
首先需要在后端配置跨域规则,允许前端域名的请求,同时处理预检请求:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// 允许前端React运行的域名,实际开发替换为真实前端地址
config.addAllowedOrigin("http://localhost:3000");
// 允许携带凭证(cookie等)
config.setAllowCredentials(true);
// 允许所有请求方法
config.addAllowedMethod("*");
// 允许所有请求头
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 对所有接口生效
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}2.2 Spring Security安全规则配置
需要放行登录接口,同时配置登录成功、失败的处理逻辑,避免默认跳转导致跨域问题:
import com.fasterxml.jackson.databind.ObjectMapper;
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.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors() // 开启跨域支持,关联上面的CorsFilter
.and()
.csrf().disable() // 前后端分离场景关闭csrf,避免POST请求被拦截
.authorizeRequests()
.antMatchers("/login").permitAll() // 放行登录接口
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login") // 登录接口地址
.successHandler(successHandler()) // 登录成功处理
.failureHandler(failureHandler()); // 登录失败处理
}
// 登录成功返回JSON,避免默认跳转
@Bean
public AuthenticationSuccessHandler successHandler() {
return (request, response, authentication) -> {
response.setContentType("application/json;charset=UTF-8");
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "登录成功");
result.put("data", authentication.getPrincipal());
new ObjectMapper().writeValue(response.getWriter(), result);
};
}
// 登录失败返回JSON
@Bean
public AuthenticationFailureHandler failureHandler() {
return (request, response, exception) -> {
response.setContentType("application/json;charset=UTF-8");
Map<String, Object> result = new HashMap<>();
result.put("code", 401);
result.put("msg", "登录失败:" + exception.getMessage());
new ObjectMapper().writeValue(response.getWriter(), result);
};
}
}三、React前端请求实现
3.1 封装登录请求方法
React前端需要使用fetch或者axios发送POST请求,注意要携带凭证,参数格式要符合Spring Security的表单登录要求:
// 使用fetch发送登录请求
const login = async (username, password) => {
const formData = new URLSearchParams();
formData.append("username", username);
formData.append("password", password);
try {
const response = await fetch("http://localhost:8080/login", {
method: "POST",
// 携带凭证,保证登录后cookie能正常传递
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: formData.toString()
});
const result = await response.json();
if (result.code === 200) {
// 登录成功,存储登录状态
localStorage.setItem("isLogin", "true");
console.log("登录成功", result.data);
} else {
console.log("登录失败", result.msg);
}
} catch (error) {
console.error("请求出错", error);
}
};
// 调用登录方法
// login("admin", "123456");3.2 登录组件示例
简单的登录表单组件,收集用户输入调用登录方法:
import React, { useState } from "react";
const LoginForm = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
// 调用上面的login方法
login(username, password);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>用户名:</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label>密码:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit">登录</button>
</form>
);
};
export default LoginForm;四、常见问题与解决
- 预检请求失败:检查后端跨域配置是否允许前端域名,是否开启了
cors()配置,预检请求的方法、头是否被允许。 - 登录请求被403拦截:确认Spring Security中是否放行了
/login接口,是否关闭了csrf,或者csrf规则是否适配前后端分离场景。 - 登录后状态无法保持:前端请求必须设置
credentials: "include",后端跨域配置必须设置setAllowCredentials(true),否则cookie无法传递。 - 参数接收不到:Spring Security表单登录默认接收
username和password字段,前端参数名要匹配,且Content-Type要使用application/x-www-form-urlencoded。
ReactSpring_Security跨域请求POST登录前后端分离修改时间:2026-05-30 23:44:19