前端脚本(AJAX/fetch/axios等)跨域

跨域是 浏览器的安全限制机制 ,核心是:前端页面(A地址)通过 AJAX/fetch 等方式请求另一个不同"源"的接口(B地址)时,浏览器会拦截该请求,导致请求失败。这是开发中非常常见的问题,尤其前后端分离项目中几乎必然遇到。

跨域的核心限制是:阻止"前端通过脚本(AJAX/fetch/axios等)发起的跨源接口请求" ------ 简单说:

  • 你可以直接在浏览器输入URL、通过<a>标签跳转、iframe嵌入等方式,访问任意域名的页面(比如从http://a.com跳转到http://b.com的页面,完全没问题);
  • 但如果http://a.com的页面里,通过JavaScript脚本请求http://b.com的接口(比如获取数据、提交表单),浏览器会触发跨域限制,拦截这个请求的响应。

一、先搞懂:什么是"同源"?什么是"跨域"?

"源"由 协议、域名、端口 三个部分组成,三者必须完全一致才叫"同源",只要有一个不同,就是"跨域"。

访问类型 是否受跨域限制? 示例场景
页面访问(非脚本) 不受限制 1. 直接输http://b.com/page打开页面; 2. a.com的页面用<a href="http://b.com/page">跳转; 3. a.com<iframe src="http://b.com/page">嵌入页面
脚本发起的接口请求 受限制(跨域拦截) 1. a.com的页面用axios.get("http://b.com/api/data"); 2. a.com的页面用fetch("http://b.com/api/submit")提交数据
举个例子(当前页面地址:http://localhost:8080):
目标接口地址 是否同源? 跨域原因
http://localhost:8080/api 协议、域名、端口完全一致
https://localhost:8080/api 协议不同(http vs https)
http://www.test.com/api 域名不同(localhost vs test.com
http://localhost:8081/api 端口不同(8080 vs 8081)
http://test.localhost:8080/api 子域名不同(无 vs test)

举个直观例子

假设:

  • 前端页面地址:http://localhost:8080(A源);
  • 后端服务地址:http://localhost:3000(B源,端口不同,跨域)。

场景1:直接访问B源的页面

  • 浏览器输入http://localhost:3000/page(B源的页面),能正常打开------不受跨域限制。

场景2:A源页面嵌入B源页面

  • http://localhost:8080的页面里写<iframe src="http://localhost:3000/page">,iframe能正常显示B源的页面------不受跨域限制。

场景3:A源页面脚本请求B源接口

  • http://localhost:8080的页面里写axios.get("http://localhost:3000/api/data")
    • 没配置CORS:浏览器控制台报错"跨域",前端拿不到数据;
    • 配置了CORS:浏览器放行响应,前端正常拿到数据。

二、为什么浏览器要限制"跨域"?(同源策略的目的)

同源策略是浏览器的核心安全机制,目的是 防止恶意网站窃取用户数据

比如:你登录了银行网站(https://bank.com),浏览器会保存银行的登录 Cookie;此时你又打开了一个恶意网站(https://hacker.com),如果没有跨域限制,这个恶意网站就能通过 AJAX 请求银行的接口,利用你已登录的 Cookie 操作你的账户------这会导致严重的安全问题。

跨域限制本质是:阻止不同源的页面,随意访问另一个源的敏感数据(Cookie、LocalStorage、接口数据等)防止恶意网站通过脚本窃取用户数据,而"页面访问"和"脚本请求"的风险完全不同:

  1. 页面访问(非脚本) :只是单纯展示对方的页面,不会主动窃取你的数据(比如你跳转到b.com,只是看b.com的内容,a.com的脚本无法通过这种跳转获取b.com的Cookie、接口数据);
  2. 脚本请求(跨源) :如果没有限制,a.com的恶意脚本可以:
    • 趁你登录b.com(浏览器保存了b.com的登录Cookie),偷偷请求b.com的接口(比如查询你的余额、修改你的信息);
    • 直接获取b.com接口返回的敏感数据(比如用户信息、订单),导致数据泄露。

所以浏览器的逻辑是:只拦截"脚本发起的跨源数据请求",不干预"单纯的页面访问" ------ 既保证安全,又不影响正常的页面跳转、嵌入等场景。

三、常见的跨域场景

  1. 前后端分离项目:前端(http://localhost:8080)请求后端接口(http://localhost:3000)(端口不同);
  2. 前端部署在静态服务器(https://xxx-cdn.com),请求后端 API(https://xxx-api.com)(域名不同);
  3. 本地开发(http://127.0.0.1:8080)请求线上接口(https://api.xxx.com)(协议+域名+端口都不同)。

四、跨域解决方案(按常用度排序,从简单到复杂)

核心思路:跨域限制是浏览器的行为,不是服务器的限制------服务器之间互相请求是没有跨域问题的,所以解决方案要么让浏览器"放行",要么绕开浏览器的限制。

1. 最常用:CORS(跨域资源共享)------ 后端配置即可

CORS 是官方推荐 的跨域解决方案,本质是 后端在响应头中声明"允许哪些源访问",浏览器收到响应后,会验证该源是否在允许列表中,若在则放行请求。

实现方式(后端配置,前端无需修改):

以 Node.js(Express)为例,核心是设置响应头:

javascript 复制代码
const express = require('express');
const app = express();

// 允许所有源跨域(开发环境可用,生产环境不推荐,建议指定具体域名)
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*'); 
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); // 允许的请求方法
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); // 允许的请求头
  next();
});

// 接口示例
app.get('/api/data', (req, res) => {
  res.send({ code: 200, data: '跨域请求成功' });
});

app.listen(3000);
关键响应头说明:
  • Access-Control-Allow-Origin:指定允许跨域的源(* 表示所有源,生产环境建议写具体域名,如 https://xxx.com);
  • Access-Control-Allow-Methods:允许的 HTTP 请求方法(GET/POST/PUT/DELETE 等);
  • Access-Control-Allow-Headers:允许的请求头(如 Content-Type、Token 等);
  • 若需要传递 Cookie(如登录态):后端需额外设置 Access-Control-Allow-Credentials: true,且 Access-Control-Allow-Origin 不能为 *(必须指定具体域名),前端请求时需设置 withCredentials: true(Axios 中配置)。
优点:简单高效,支持所有 HTTP 方法,生产环境推荐;缺点:需要后端配合配置。
2. 开发环境专用:代理转发(前端配置)

开发时,前端本地启动的服务(如 http://localhost:8080)请求跨域接口,可通过 前端代理服务器转发请求------因为代理服务器是"服务器之间的请求",没有跨域限制。

实现方式(以 Vue/React 项目为例):
  • Vue 项目(vue.config.js):

    javascript 复制代码
    module.exports = {
      devServer: {
        proxy: {
          // 匹配所有以 /api 开头的请求
          '/api': {
            target: 'http://localhost:3000', // 后端接口的真实地址
            changeOrigin: true, // 开启代理,模拟后端接口的源
            pathRewrite: { '^/api': '' } // 去掉请求路径中的 /api(可选,根据后端接口路径调整)
          }
        }
      }
    };
  • React 项目(package.json):

    json 复制代码
    "proxy": "http://localhost:3000" // 直接指定后端接口地址(简单场景)
    // 复杂场景需用 http-proxy-middleware 配置
原理:

前端请求 http://localhost:8080/api/data → 代理服务器(前端本地服务)转发到 http://localhost:3000/data → 后端返回数据 → 代理服务器再把数据返回给前端。

浏览器看到的是"同源请求"(都是 localhost:8080),所以不拦截。

优点:前端独立配置,无需后端参与,开发环境必备;缺点:仅适用于开发环境,生产环境需另寻方案。
3. 兼容旧浏览器:JSONP

JSONP 是早期的跨域方案,利用 <script> 标签不受同源策略限制 的特性实现(<script> 可以加载任意域名的脚本)。

实现方式(前后端配合):
  • 前端:创建 <script> 标签,请求后端接口并传入回调函数名;
  • 后端:返回回调函数调用的脚本,将数据作为参数传入。

示例:

javascript 复制代码
// 前端代码
function handleData(data) {
  console.log('跨域数据:', data); // 接收后端返回的数据
}

// 创建 script 标签,发起请求
const script = document.createElement('script');
script.src = 'http://localhost:3000/api/jsonp?callback=handleData'; // 传入回调函数名
document.body.appendChild(script);

// 后端代码(Node.js)
app.get('/api/jsonp', (req, res) => {
  const callback = req.query.callback; // 获取前端传入的回调函数名
  const data = JSON.stringify({ code: 200, data: 'JSONP 跨域成功' });
  res.send(`${callback}(${data})`); // 返回:handleData({"code":200,"data":"JSONP 跨域成功"})
});
优点:兼容 IE 等旧浏览器;缺点:仅支持 GET 请求,安全性较低(可能遭受 XSS 攻击),现在很少用。
4. 其他方案(适用于特殊场景):
  • document.domain + iframe :仅适用于"主域名相同、子域名不同"的场景(如 a.xxx.comb.xxx.com),通过设置 document.domain = 'xxx.com' 实现同源;
  • postMessage :适用于两个不同源的页面之间通信(如 iframe 嵌套场景),通过 window.postMessage() 发送数据,window.addEventListener('message') 接收数据;
  • Nginx 反向代理:生产环境常用,通过 Nginx 服务器转发前端请求到后端接口,本质和"前端代理"原理一致,但部署在服务器端(适合生产环境)。

示例(Nginx 配置):

nginx 复制代码
server {
  listen 80;
  server_name localhost;

  # 前端页面请求 /api 时,转发到后端接口
  location /api {
    proxy_pass http://localhost:3000; # 后端接口地址
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
  }

  # 静态资源(前端页面)
  location / {
    root /usr/share/nginx/html;
    index index.html;
  }
}

五、跨域避坑要点

  1. 生产环境不要用 Access-Control-Allow-Origin: * + 传递 Cookie,会导致跨域失败(浏览器不允许);
  2. 跨域请求可能会触发"预检请求(OPTIONS)":当请求方法不是 GET/POST,或请求头包含自定义字段(如 Token)时,浏览器会先发送 OPTIONS 请求验证后端是否允许跨域,后端需正确响应 OPTIONS 请求;
  3. JSONP 仅支持 GET 请求,且要注意 XSS 防护(后端需过滤回调函数名和返回数据);
  4. 代理转发仅适用于开发环境,生产环境建议用 CORS 或 Nginx 反向代理。

六、关键误区澄清:"跨域拦截"不是"服务器拒绝访问"

很多人会误以为"跨域就是服务器不让访问",但实际情况是:

  • 脚本发起的跨源请求 已经发送到服务器了(服务器会处理并返回响应);
  • 浏览器在接收响应后,发现不符合CORS规则,才主动拦截了这个响应,不让前端脚本获取数据(前端会看到"Access-Control-Allow-Origin"相关的错误)。

比如之前你用Express/Spring Boot配置的跨域服务:

  • 没配置CORS时,前端请求http://localhost:3000/api/data,服务器已经返回了{code:200, data:"跨域请求成功"},但浏览器拦截了这个响应;
  • 配置CORS后,浏览器验证响应头的Access-Control-Allow-Origin符合规则,才放行响应,让前端脚本拿到数据。

总结

  • 开发环境:优先用 前端代理转发(Vue/React 自带配置),无需后端参与;
  • 生产环境:优先用 CORS (后端配置,简单高效),或 Nginx 反向代理(适合需要统一部署的场景);
  • 兼容旧浏览器:可用 JSONP(仅支持 GET,不推荐优先使用)。

跨域限制的核心是:阻止"前端脚本跨源获取数据",不阻止"单纯的跨源页面访问"

  • 能做的:直接访问、跳转、嵌入任意域名的页面;
  • 不能做的(无CORS配置时):在A源的页面里,用JavaScript脚本请求B源的接口并获取数据。

这也是为什么前后端分离项目必须配置CORS(或代理)------ 因为前端需要通过脚本请求后端接口,而直接访问页面、跳转等场景完全不需要处理跨域。

举例

vue-cli使用axios跨域访问服务和Nginx配置

相关推荐
少卿4 小时前
React Compiler 完全指南:自动化性能优化的未来
前端·javascript
爱隐身的官人4 小时前
beef-xss hook.js访问失败500错误
javascript·xss
军军3605 小时前
从图片到点阵:用JavaScript重现复古数码点阵艺术图
前端·javascript
znhy@1235 小时前
Vue基础知识(一)
前端·javascript·vue.js
学习吖5 小时前
vue中封装的函数常用方法(持续更新)
大数据·javascript·vue.js·ajax·前端框架
范特东南西北风6 小时前
Wappalyzer 原型链漏洞问题完整解决过程
前端·javascript
fruge6 小时前
自制浏览器插件:实现网页内容高亮、自动整理收藏夹功能
开发语言·前端·javascript
英俊潇洒的码农7 小时前
Array.isArray()性能测试
前端·javascript
chilavert3187 小时前
技术演进中的开发沉思-200 JavaScript:YUI 的AJAX 动态加载机制
javascript·ajax·okhttp
囨誌7 小时前
vben admin表格常用配置
前端·javascript·html