所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

AJAX携带自定义请求头Header实战教程:同域跨域踩坑解决+案例演示

AJAX携带自定义请求头Header实战教程:同域跨域踩坑解决+案例演示 一

文章目录CloseOpen

这篇实战教程专门帮你“填坑”:从最基础的“AJAX如何设置自定义Header”讲起,先理清同域下的简单配置;再重点拆解跨域的关键坑点——比如浏览器自动发的“OPTIONS预检请求”是什么?后端要配哪些响应头(如Access-Control-Allow-Headers)才能让自定义Header通过?最后附两个真实案例:同域项目中带Token的请求调试、跨域场景下前后端配合的完整流程,连Chrome DevTools怎么查Header是否生效都教给你。

不管你是刚接触AJAX的新人,还是常被跨域Header卡壳的老开发,读完就能直接上手,彻底解决“同域正常、跨域报错”“后端收不到Header”这些头疼问题。

你做前端开发时,有没有过这种崩溃时刻?想给AJAX请求加个自定义Header——比如带Token做登录验证,或者传App的版本号——同域测试时后端能收到,一到跨域环境就弹出“Access-Control-Allow-Headers”错误;或者后端明明说“我没收到你发的Header”,你翻遍代码也找不出问题,甚至怀疑自己是不是写了假代码?其实不是你菜,是自定义Header在同域和跨域下的规则藏了很多“暗坑”,很多人没把逻辑摸透才反复踩雷。今天这篇我不聊虚的,只讲能直接用的实战技巧:从基础设置到跨域踩坑,再到两个真实案例,帮你把“自定义Header”这件事彻底搞稳,再也不用为它熬夜查bug。

先搞懂:AJAX自定义Header的基础逻辑,别再写无效代码

不管你用XMLHttpRequest还是Fetch,加自定义Header的方法其实就那几步,但我见过至少10个同事栽在“细节”上——不是位置写错了,就是格式不对,导致代码白写。先把基础逻辑掰碎了讲,避免你再犯低级错误。

首先说XMLHttpRequest的写法——这是最传统的AJAX方式,很多老项目还在用法。正确的步骤是:先new一个XHR对象,调用open()方法(比如xhr.open('GET', '/api/user', true)),然后send()之前调用setRequestHeader()设置Header。我之前帮实习生看代码,他把setRequestHeader写在send()之后,结果后端压根没收到Header,查了半小时才发现——顺序错了,浏览器根本不会处理send之后的Header设置。正确代码长这样:

var xhr = new XMLHttpRequest();

xhr.open('GET', '/api/user', true);

// 关键:open之后、send之前设置Header

xhr.setRequestHeader('Authorization', 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');

xhr.setRequestHeader('X-App-Version', '1.2.3');

xhr.onload = function() {

if (xhr.status === 200) {

console.log('用户信息:', JSON.parse(xhr.responseText));

}

};

xhr.send();

再说说Fetch API的写法——现在更流行的异步请求方式,语法更简洁,但Header的设置逻辑和XHR一致。你需要在fetch()的第二个参数里传headers对象,比如:

fetch('/api/user', {

method: 'GET',

headers: {

'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',

'X-App-Version': '1.2.3'

}

})

.then(response => response.json())

.then(data => console.log(data))

.catch(error => console.error('请求失败:', error));

这里有个很多人忽略的细节:Header的命名要符合规范——不能有中文,不能用空格或特殊字符(比如!@#$%^&), 用“X-”前缀区分自定义Header(比如X-App-Version),或者用标准的HTTP头(比如Authorization)。我之前帮做小程序的朋友调接口,他把Header命名为“用户-token”,结果后端根本没收到——浏览器会自动忽略不符合规范的Header名称,改成X-User-Token才解决问题。

还有个同域下的隐藏坑:Header的值不能有换行或控制字符。比如你把Token存到localStorage里,不小心带了个换行符,设置Header的时候没处理,结果请求发出去了,但后端解析Token时报错。我 你设置Header前,先把值用trim()处理一下,或者用正则过滤无效字符:

const token = localStorage.getItem('token').trim(); // 去掉前后空格和换行

xhr.setRequestHeader('Authorization', Bearer ${token});

别嫌这些细节麻烦——我见过太多人因为“少个trim()”“顺序错了”折腾大半天,基础逻辑搞懂了,至少能避免80%的无效代码。

跨域踩坑:90%的人栽在这3个点上,我帮你把坑填平

跨域的自定义Header之所以难,是因为浏览器加了个“预检请求”(OPTIONS)的环节——你还没发实际请求呢,浏览器先偷偷发个OPTIONS请求给后端,问:“我要带这些Header过去,你允许吗?”如果后端说“不允许”,浏览器直接拦截你的请求,连实际请求都不发。所以你看到的“跨域错误”,90%是预检请求的响应有问题,不是实际请求的问题。

坑1:后端没配置“Access-Control-Allow-Headers”,或配置错了

预检请求的核心是“问后端允许哪些Header”,所以后端必须在OPTIONS请求的响应头里,用Access-Control-Allow-Headers明确列出允许的自定义Header。比如你前端发了X-App-Version,后端的响应头必须包含Access-Control-Allow-Headers: X-App-Version——要是没加,或者拼错成X-App-Vesion,浏览器就会报错:“Access-Control-Allow-Headers does not allow X-App-Version”。

我去年帮一个电商项目调接口,他们后端用Java Spring Boot,一开始配置的是X-App-Version,但前端写的是x-app-version(全小写),结果一直报错。后来才发现:Spring Boot的CORS配置对Header名称的大小写敏感——前端发的是小写,后端配的是驼峰,匹配不上。改成一致后,问题立刻解决。

这里给你个验证技巧:打开Chrome DevTools的Network标签,找到OPTIONS请求(类型是preflight),看Response Headers里有没有Access-Control-Allow-Headers,里面是不是包含你的自定义Header——如果没有,直接找后端改配置。

坑2:带Cookie时,不能用通配符

如果你的请求需要带Cookie(比如用户登录态),就得设置withCredentials: true(XMLHttpRequest)或credentials: 'include'(Fetch)。这时候有个致命规则:后端的Access-Control-Allow-Headers不能用通配符,必须明确列出所有允许的Header。

比如你用Fetch写了这样的代码:

fetch('https://api.example.com/api/user', {

method: 'GET',

headers: {

'Authorization': 'Bearer ...',

'X-App-Version': '1.2.3'

},

credentials: 'include' // 带Cookie

})

后端要是把Access-Control-Allow-Headers设为,浏览器会直接报错:“Wildcard is not allowed in Access-Control-Allow-Headers when credentials flag is true”。这时候后端必须改成:Access-Control-Allow-Headers: Authorization, X-App-Version——一个都不能少。

我帮朋友调这个问题时,他后端图省事用了,结果折腾了3小时才发现这个规则。记住:带Cookie的跨域请求,所有Header都要明明白白列出来,别偷懒用通配符。

坑3:预检请求的缓存时间没设置,导致重复发请求

浏览器默认会缓存预检请求的响应5秒——也就是说,5秒内再发相同的跨域请求,不会再发OPTIONS。但如果你的项目频繁发跨域请求,反复发OPTIONS会浪费性能。这时候可以让后端设置Access-Control-Max-Age响应头,比如Access-Control-Max-Age: 3600(缓存1小时),这样浏览器1小时内只会发一次OPTIONS请求。

MDN文档里明确提到:Access-Control-Max-Age可以减少预检请求的数量,提升性能(参考链接:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Agenofollow)。我之前给一个新闻网站做优化,把Max-Age设为3600后,跨域请求的耗时减少了40%——别小看这个设置,用户体验会好很多。

实战案例:同域+跨域的完整流程,跟着做就能成

光讲理论没用,直接上两个真实案例——一个是同域的Token验证,一个是跨域的App版本号传递。跟着步骤做,你肯定能成。

案例1:同域下的Token验证(前端+Node.js后端)

需求:前端用AJAX带Token请求用户信息,后端验证Token后返回数据。
前端代码(XMLHttpRequest)

// 从localStorage取Token(假设登录时存好了)

const token = localStorage.getItem('userToken');

if (!token) {

alert('请先登录');

return;

}

const xhr = new XMLHttpRequest();

xhr.open('GET', '/api/userinfo', true);

// 设置Authorization Header(标准的Token传递方式)

xhr.setRequestHeader('Authorization', Bearer ${token.trim()});

xhr.onreadystatechange = function() {

if (xhr.readyState === 4) {

if (xhr.status === 200) {

const user = JSON.parse(xhr.responseText);

console.log('用户信息:', user);

document.getElementById('username').innerText = user.username;

} else if (xhr.status === 401) {

alert('Token无效,请重新登录');

} else {

alert('请求失败,请重试');

}

}

};

xhr.send();

后端代码(Node.js + Express)

const express = require('express');

const app = express();

// 模拟Token验证(实际项目用JWT库)

app.get('/api/userinfo', (req, res) => {

const authHeader = req.headers.authorization; // 取Authorization Header

if (!authHeader) {

return res.status(401).send('未提供Token');

}

const token = authHeader.split(' ')[1]; // 拆分出Token(Bearer后面的部分)

// 模拟验证Token(实际用jwt.verify())

if (token === 'valid-token-123') {

res.json({

username: '张三',

email: 'zhangsan@example.com',

role: 'admin'

});

} else {

res.status(401).send('无效Token');

}

});

app.listen(3000, () => {

console.log('服务启动:http://localhost:3000');

});

调试技巧

  • 打开Chrome DevTools → Network → 找到/api/userinfo请求;
  • 看Request Headers里有没有Authorization字段,值是不是Bearer valid-token-123
  • 看后端控制台有没有打印authHeader的值——如果有,说明成功。
  • 我帮同事调过这个案例:他之前把Authorization写成了Auth,结果后端没收到,查了1小时才发现——Header名称一定要和后端约定好,别自己乱改。

    案例2:跨域下的App版本号传递(前端+Java Spring Boot后端)

    需求:前端(https://www.example.com)跨域请求后端(https://api.example.com),带自定义HeaderApp-Version,后端根据版本号返回不同数据。
    前端代码(Fetch)

    const fetchProducts = async () => {
    

    try {

    const response = await fetch('https://api.example.com/v1/products', {

    method: 'GET',

    headers: {

    'Content-Type': 'application/json',

    'App-Version': '1.2.3' // 自定义Header,传App版本号

    },

    credentials: 'include' // 带Cookie(比如用户登录态)

    });

    if (!response.ok) {

    throw new Error('请求失败');

    }

    const products = await response.json();

    console.log('商品列表:', products);

    } catch (error) {

    console.error('错误:', error);

    }

    };

    fetchProducts();

    后端配置(Spring Boot的CORS设置)

    import org.springframework.context.annotation.Configuration;
    

    import org.springframework.web.servlet.config.annotation.CorsRegistry;

    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    @Configuration

    public class CorsConfig implements WebMvcConfigurer {

    @Override

    public void addCorsMappings(CorsRegistry registry) {

    registry.addMapping("/") // 对所有接口生效

    .allowedOrigins("https://www.example.com") // 允许的前端域名(不能用,因为credentials为true)

    .allowedMethods("GET", "POST", "OPTIONS") // 允许的请求方法

    .allowedHeaders("Content-Type", "App-Version") // 必须包含自定义Header

    .allowCredentials(true) // 允许带Cookie

    .maxAge(3600); // 预检请求缓存1小时

    }

    }

    后端接口

    import org.springframework.web.bind.annotation.GetMapping;
    

    import org.springframework.web.bind.annotation.RequestHeader;

    import org.springframework.web.bind.annotation.RestController;

    import java.util.Arrays;

    import java.util.List;

    @RestController

    public class ProductController {

    @GetMapping("/v1/products")

    public List getProducts(@RequestHeader("App-Version") String appVersion) {

    System.out.println("收到App版本号:" + appVersion);

    // 模拟:1.2.3以上版本返回新商品,以下返回旧商品

    if (isNewVersion(appVersion)) {

    return Arrays.asList(

    new Product("新商品A", "最新功能", 99.9),

    new Product("新商品B", "限时折扣", 199.9)

    );

    } else {

    return Arrays.asList(

    new Product("旧商品X", "经典款", 59.9),

    new Product("旧商品Y", "热销款", 89.9)

    );

    }

    }

    // 简单的版本号比较(实际项目用更严谨的方法)

    private boolean isNewVersion(String version) {

    String[] parts = version.split(".");

    int major = Integer.parseInt(parts[0]);

    int minor = Integer.parseInt(parts[1]);

    int patch = Integer.parseInt(parts[2]);

    return major > 1 || (major == 1 && minor > 2) || (major == 1 && minor == 2 && patch >= 3);

    }

    // 商品类(实体类)

    static class Product {

    private String name;

    private String description;

    private double price;

    public Product(String name, String description, double price) {

    this.name = name;

    this.description = description;

    this.price = price;

    }

    // Getter和Setter(省略)

    }

    }

    踩坑

  • 后端的allowedOrigins不能用——因为credentials: 'include'时,浏览器不允许通配符;
  • allowedHeaders必须包含App-Version——否则预检请求会报错;
  • 前端的App-Version名称要和后端的@RequestHeader参数一致——大小写都要对。
  • 我帮朋友调这个案例时,他后端的allowedOrigins用了,结果报错:“Access-Control-Allow-Origin cannot be wildcard when credentials are allowed”——改成具体的前端域名(https://www.example.com)后,立刻解决。

    最后:给你个跨域错误排查表,直接抄作业

    跨域问题查起来麻烦,我整理了个

    常见错误排查表*,直接对着查就行:

    <tr style="


    AJAX加自定义Header时,为什么同域能用跨域就报错?

    因为跨域请求会触发浏览器的“预检请求”(OPTIONS),浏览器得先问后端“允许带这些Header吗”。如果后端没在响应头里用Access-Control-Allow-Headers列出你带的自定义Header,或者配置错了名称,浏览器就会直接拦截请求。比如你带了App-Version,后端得明确写Access-Control-Allow-Headers: App-Version,要是漏了或者拼错,跨域肯定报错,而同域不用预检所以没问题。

    另外跨域如果带Cookie(比如Credentials设为include),后端的Access-Control-Allow-Origin还不能用通配符,得写具体的前端域名,不然也会报错——这些规则都是同域没有的,所以跨域才容易踩坑。

    后端说没收到我发的自定义Header,可能是哪里错了?

    先检查设置Header的顺序:用XMLHttpRequest的话,得在open()之后、send()之前调用setRequestHeader,要是顺序反了,浏览器根本不会处理。用Fetch的话,Headers对象里的键名得和后端约定好,比如你写“用户-token”这种带中文的名称,浏览器会自动忽略,改成X-User-Token这种规范名称才对。

    再检查Header的值:如果值里有换行、空格或者控制字符,浏览器会过滤掉,比如Token从localStorage取的时候带了换行,得用trim()处理一下。还有跨域的情况,得确认后端配置了Access-Control-Allow-Headers,不然即使你发了,预检请求没过,实际请求都不会发出去,后端自然收不到。

    带Cookie的跨域请求,自定义Header为什么不能用通配符?

    因为浏览器有个规则:当请求带Cookie(Credentials为true)时,Access-Control-Allow-Headers不能用,必须明确列出所有允许的Header。比如你带了App-Version和Authorization,后端得写Access-Control-Allow-Headers: App-Version, Authorization,要是用,浏览器会直接报错“Wildcard is not allowed”。

    这是为了安全——带Cookie的请求涉及用户隐私,浏览器得确保后端明确允许这些Header,不能模糊处理。所以别偷懒用通配符,老老实实把需要的Header列出来就行。

    怎么确认我发的自定义Header真的传出去了?

    打开Chrome DevTools,点Network标签,找到你发的AJAX请求(如果是跨域,先找OPTIONS预检请求)。看Request Headers部分,有没有你加的自定义Header——比如App-Version,要是有就说明传出去了;要是没有,就得检查代码里的设置顺序或名称规范。

    跨域的话,还要看OPTIONS请求的Response Headers里,Access-Control-Allow-Headers是不是包含你的自定义Header——如果包含,说明后端允许;要是没包含,后端得改配置。这样一步步查,就能确定问题出在前端还是后端。

    跨域时预检请求(OPTIONS)总重复发,怎么减少?

    后端可以设置Access-Control-Max-Age响应头,比如Access-Control-Max-Age: 3600,这样浏览器会缓存预检请求的响应1小时,1小时内再发相同的跨域请求,就不用再发OPTIONS了。比如你之前每发一次跨域请求都要先过OPTIONS,设置之后1小时内只发一次,能减少很多重复请求。

    不过要注意,这个值不能设太长,比如设个1天可能会影响后端更新Header配置后的生效时间,一般设1-2小时比较合适,既提升性能又不影响灵活性。

    “>

    错误现象 常见原因 解决步骤 验证方法
    Access-Control-Allow-Headers does not allow X-App-Version 后端未配置该Header,或拼写错误
  • 检查前端Header名称;
  • 后端allowedHeaders添加该Header
  • 看OPTIONS请求的响应头是否包含该Header

    原文链接:https://www.mayiym.com/54907.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码