React Axios对接Spring Boot Security返回401且缺失Authorization头
React + Spring Boot 项目Axios请求401故障修复方案
问题概述
- 技术栈:React前端 + Spring Boot后端,通过Axios调用后端接口
- 前置处理:已初步配置Spring Security解决CORS跨域拦截问题,暂时使用默认账号
user+服务启动生成的随机密码做接口测试(自定义账号密码逻辑后续单独调试) - 环境信息:后端服务运行在
localhost:9898,前端运行在localhost:3000 - 故障现象:
- 浏览器直接访问GET接口
http://localhost:9898/api/Contact/1可正常返回数据,抓包可见请求携带Authorization头 - 前端Axios调用该接口返回401未授权,抓包发现请求未携带配置的Authorization头
- 尝试配置放行OPTIONS预检请求时出现服务启动后短时间崩溃问题,查阅官方文档、社区教程均未定位根因
- 浏览器直接访问GET接口
现有问题代码
后端Spring Security配置
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); http.cors().and(); } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); configuration.setAllowedMethods(Arrays.asList("GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
后端Controller代码
@CrossOrigin(origins = "http:localhost:3000") @RestController @RequestMapping("/api/Contact") public class ContactController { @Autowired private ContactRepository ContactRepository; @GetMapping("/") public List<Contact> getContacts(){ return this.ContactRepository.findAll(); } @GetMapping("/{id}") public Contact GetContact(@PathVariable Long id, HttpServletResponse response){ response.setHeader("Access-Control-Allow-Origin", "**"); return ContactRepository.findById(id).orElse(null); } }
前端Axios实例配置
import axios from "axios"; const api = axios.create({ baseURL: "http://localhost:9898", }); // api.defaults.headers.common = { // 'X-Requested-With': 'XMLHttpRequest' // }; export default axios;
前端业务请求代码
const tok = 'user:9de3a921-a4af-4d51-b8d7-cf37b208916e'; const hash = btoa(tok); const Basic = 'Basic ' + hash; const headers = { "Cache-Control": "no-cache", "Accept-Language": "en", "Content-Type": "application/json", "Access-Control-Allow-Origin": "http://localhost:3000", "Access-Control-Allow-Methods": "DELETE, POST, GET, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With", //"Authorization": "Basic dXNlcjowM2VhN2JhYS1mMTQ0LTQ5YWMtOGFhMy02NDE4YWJiNzdhMTk=", 'Authorization': `Basic ${hash}`, }; useEffect(() =>{ console.log(Basic); axios.get("http://localhost:9898/api/Contact/1", headers) .then((res) => { console.log("data " + res.data); console.log("response header " + res.headers['Authorization']); setInfo(res.data); }).catch(err => console.log("error found " + err)); console.log(info); }, []||[]);
根因定位
- 前端Axios传参格式错误:
axios.get第二个参数为全局配置对象,请求头需要包裹在{headers: {}}结构内传入,现有代码直接传入headers对象,Axios无法识别到自定义请求头,因此Authorization头未被携带 - CORS配置缺失关键规则:全局CORS配置未显式声明
allowedHeaders,跨域场景下Authorization属于自定义请求头,未被允许时浏览器会拦截头注入、拦截预检请求 - 代码笔误:Controller上
@CrossOrigin注解配置的源地址写为http:localhost:3000,缺少双斜杠为无效地址;接口内手动添加的Access-Control-Allow-Origin: **为无效值,跨域通配符为单星号*,且与全局配置重复无实际作用 - Spring Security配置顺序与规则错误:CORS配置写在httpBasic认证规则之后,未关闭默认开启的CSRF防护,OPTIONS预检请求未正确放行,是之前配置后服务崩溃、请求被拦截的核心原因
- 无效请求头配置:前端请求中添加的
Access-Control-Allow-*系列字段为服务端返回的响应头,前端携带此类字段无任何作用,反而可能触发CORS校验失败 - Axios实例无效:创建了自定义baseURL的api实例,但最终导出的是原生axios对象,自定义配置未生效
分步修复方案
1. 修复后端Spring Security配置
调整配置顺序,关闭CSRF,正确放行OPTIONS预检请求,补全CORS允许头规则:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 前后端分离场景关闭CSRF防护 .csrf().disable() // 先加载CORS配置 .cors().and() .authorizeRequests() // 放行所有OPTIONS预检请求,不做认证拦截 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 其余所有接口需要认证 .anyRequest().authenticated() .and() .httpBasic(); } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); configuration.setAllowedMethods(Arrays.asList("GET", "PUT", "POST", "DELETE", "OPTIONS", "HEAD")); // 允许所有请求头,包含Authorization configuration.setAllowedHeaders(Arrays.asList("*")); // 后续需要携带Cookie等凭证时打开以下配置,此时allowedOrigins不能使用通配符 // configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
2. 简化Controller配置
删除重复、错误的跨域配置,全局CORS已生效时可直接删除类上的@CrossOrigin注解:
@RestController @RequestMapping("/api/Contact") public class ContactController { @Autowired private ContactRepository ContactRepository; @GetMapping("/") public List<Contact> getContacts(){ return this.ContactRepository.findAll(); } @GetMapping("/{id}") public Contact GetContact(@PathVariable Long id){ return ContactRepository.findById(id).orElse(null); } }
3. 修复前端Axios配置
导出已创建的自定义Axios实例,通用配置可直接写入实例:
import axios from "axios"; const api = axios.create({ baseURL: "http://localhost:9898", headers: { 'X-Requested-With': 'XMLHttpRequest' } }); export default api;
4. 修复前端业务请求逻辑
修正Axios传参格式,删除无效的CORS响应头配置,修正useEffect依赖写法:
// 注意:每次重启后端服务控制台会打印新的随机密码,需替换为最新值 const tok = 'user:9de3a921-a4af-4d51-b8d7-cf37b208916e'; const hash = btoa(tok); const authToken = 'Basic ' + hash; useEffect(() =>{ // 使用自定义api实例,无需写全接口前缀 api.get("/api/Contact/1", { headers: { "Cache-Control": "no-cache", "Accept-Language": "en", "Content-Type": "application/json", 'Authorization': authToken } }) .then((res) => { console.log("接口返回数据:", res.data); setInfo(res.data); }).catch(err => console.log("请求异常:", err)); // 依赖数组直接传空数组即可,表示组件挂载时仅执行一次 }, []);
注:原代码中在请求逻辑后立刻打印
info无法拿到最新值,React状态更新为异步行为,需通过单独的useEffect监听info变化才能获取更新后的值。
验证方式
- 重启后端服务,确认启动日志中生成的随机密码与前端配置一致
- 刷新前端页面,查看浏览器控制台无CORS、401报错
- 抓包确认OPTIONS预检请求返回200状态码,正式GET请求携带Authorization头
- 确认页面正常渲染接口返回的联系人信息
内容的提问来源于stack exchange,提问作者arupkey
相关产品推荐
相关产品推荐

