You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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预检请求时出现服务启动后短时间崩溃问题,查阅官方文档、社区教程均未定位根因

现有问题代码

后端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);
  }, []||[]);

根因定位

  1. 前端Axios传参格式错误:axios.get第二个参数为全局配置对象,请求头需要包裹在{headers: {}}结构内传入,现有代码直接传入headers对象,Axios无法识别到自定义请求头,因此Authorization头未被携带
  2. CORS配置缺失关键规则:全局CORS配置未显式声明allowedHeaders,跨域场景下Authorization属于自定义请求头,未被允许时浏览器会拦截头注入、拦截预检请求
  3. 代码笔误:Controller上@CrossOrigin注解配置的源地址写为http:localhost:3000,缺少双斜杠为无效地址;接口内手动添加的Access-Control-Allow-Origin: **为无效值,跨域通配符为单星号*,且与全局配置重复无实际作用
  4. Spring Security配置顺序与规则错误:CORS配置写在httpBasic认证规则之后,未关闭默认开启的CSRF防护,OPTIONS预检请求未正确放行,是之前配置后服务崩溃、请求被拦截的核心原因
  5. 无效请求头配置:前端请求中添加的Access-Control-Allow-*系列字段为服务端返回的响应头,前端携带此类字段无任何作用,反而可能触发CORS校验失败
  6. 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变化才能获取更新后的值。

验证方式

  1. 重启后端服务,确认启动日志中生成的随机密码与前端配置一致
  2. 刷新前端页面,查看浏览器控制台无CORS、401报错
  3. 抓包确认OPTIONS预检请求返回200状态码,正式GET请求携带Authorization头
  4. 确认页面正常渲染接口返回的联系人信息

内容的提问来源于stack exchange,提问作者arupkey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 15:09:21