一文搞懂跨域:从原理到解决方案

前言

在前端开发中,"跨域" 是一个非常常见的问题。

当你在控制台看到 "Access to fetch at 'xxx' from origin 'xxx' has been blocked by CORS policy" 这样的错误时,就意味着遇到了跨域问题。

本文讲解跨域的本质、常见解决方案以及各自的适用场景。

什么是跨域?

简单来说,当一个网页从一个域名的资源请求 另一个域名的资源时,就会产生跨域。

这里的 "域" 是由协议、域名和端口共同组成的,只要其中任何一个不同,就被视为不同的域。

  • http://www.example.com 访问 https://www.example.com(协议不同)
  • http://www.example.com 访问 http://www.another.com(域名不同)
  • http://www.example.com:80 访问 http://www.example.com:8080(端口不同)

浏览器出于安全考虑,会限制跨域的网络请求,这就是 "同源策略"。

同源策略是浏览器的一种安全机制,它阻止不同源的网页之间进行某些交互,比如读取对方的 Cookie、LocalStorage 等。

跨域解决方案

1. CORS:现代浏览器的标准方案

CORS(Cross-Origin Resource Sharing,跨域资源共享)是基于 HTTP 1.1 的跨域解决方案,也是目前最推荐的方式。

它的核心思想是:如果浏览器要跨域访问服务器的资源,需要获得服务器的允许

根据请求的不同类型,CORS 定义了三种交互模式:

简单请求

当请求同时满足以下条件时,被视为简单请求:

  1. 请求方法为 GET、POST 或 HEAD
  2. 请求头仅包含安全字段(如 Accept、Content-Type 等)
  3. 若包含 Content-Type,只能是 text/plain、multipart/form-data 或 application/x-www-form-urlencoded

简单请求的交互流程:

  • 浏览器自动在请求头添加 Origin 字段,表明请求来源
  • 服务器在响应头中添加 Access-Control-Allow-Origin 字段,指定允许的来源
  • 浏览器检查响应头,如果允许当前源访问,就将响应交给 JS 处理
// 简单请求示例
fetch('http://crossdomain.com/api/news');
需要预检的请求

不符合简单请求条件的请求会被视为 "需要预检的请求",这类请求会先发送一个预检请求(OPTIONS 方法),询问服务器是否允许:

  1. 浏览器发送预检请求,包含:

    • Origin:请求来源
    • Access-Control-Request-Method:后续真实请求的方法
    • Access-Control-Request-Headers:后续真实请求会使用的请求头
  2. 服务器响应预检请求,包含:

    • Access-Control-Allow-Origin:允许的来源
    • Access-Control-Allow-Methods:允许的请求方法
    • Access-Control-Allow-Headers:允许的请求头
    • Access-Control-Max-Age:预检结果的缓存时间(秒)
  3. 预检通过后,浏览器发送真实请求

  4. 服务器响应真实请求

// 需要预检的请求示例
fetch('http://crossdomain.com/api/user', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value'
  },
  body: JSON.stringify({ name: '张三' })
});
附带身份凭证的请求

默认情况下,跨域请求不会附带 Cookie 等身份凭证。如果需要附带,可以这样设置:

// XMLHttpRequest
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

// fetch API
fetch(url, {
  credentials: 'include'
});

此时服务器需要在响应头中添加:

Access-Control-Allow-Credentials: true

注意:这种情况下,服务器不能将 Access-Control-Allow-Origin 设置为*,必须指定具体的源。

2. JSONP:古老但仍在使用的方案

在 CORS 出现之前,JSONP 是实现跨域的常用方法。

它利用了 script 标签不受同源策略限制的特性。

JSONP 的工作原理:

  1. 客户端定义一个处理数据的函数
  2. 动态创建一个 script 标签,其 src 指向跨域的 API 地址,并通过 URL 参数指定回调函数名
  3. 服务器返回一段 JS 代码,内容是调用该回调函数,并将数据作为参数传入
  4. 浏览器执行这段 JS 代码,从而触发回调函数,获取数据
// 客户端代码
function handleData(data) {
  console.log('获取到的数据:', data);
}

// 创建script标签
var script = document.createElement('script');
script.src = 'http://crossdomain.com/api/data?callback=handleData';
document.body.appendChild(script);

服务器返回的响应会是这样的:

handleData({ name: '张三', age: 18 });

JSONP 的缺点很明显:只能支持 GET 请求,安全性较差,不推荐在新项目中使用。

3. 代理:开发环境常用方案

代理是开发阶段解决跨域问题的常用手段。

它的原理是:由于浏览器有同源策略限制,但服务器之间没有,所以可以设置一个代理服务器,让它作为中间层转发请求

具体做法是:

  1. 前端请求本地服务器(同源,不会跨域)
  2. 本地服务器将请求转发到目标服务器
  3. 目标服务器返回数据给本地服务器
  4. 本地服务器将数据返回给前端

在开发环境中,可以很方便地配置代理:

Vue 项目配置代理(vue.config.js)
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'http://crossdomain.com', // 目标服务器地址
        changeOrigin: true, // 改变请求头中的Origin
        pathRewrite: {
          '^/api': '' // 去掉请求路径中的/api前缀
        }
      }
    }
  }
};
React 项目配置代理(package.json)
{
  "proxy": "http://crossdomain.com"
}

使用代理后,前端请求代码可以这样写:

// 原本需要跨域的请求
// fetch('http://crossdomain.com/data')

// 使用代理后的请求
fetch('/api/data') // 实际会被代理到http://crossdomain.com/data

代理方案只适合开发环境,生产环境通常需要在服务器端配置 CORS。

总结

  • CORS:推荐在生产环境使用,支持各种 HTTP 方法,安全性好
  • JSONP:兼容性好(支持古老浏览器),但只支持 GET 请求,安全性差,不推荐新项目使用
  • 代理:主要用于开发环境,简单易用,无需修改后端代码

评论 11
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十八朵郁金香

感恩前行路上有你相伴

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值