跨域问题和解决方案

跨域问题及解决方案

同源策略及跨域问题

同源策略 是一套浏览器安全机制 ,当一个 的文档和脚本,与另一个的资源进行通信时,同源策略就会对这个通信做出不同程度的限制。

简单来说,同源策略对 同源资源 放行 ,对 异源资源 限制

因此限制造成的开发问题,称之为跨域(异源)问题

同源和异源

复制代码
源(origin) = 协议 + 域名 + 端口

例如:

https://study.duyiedu.com/api/movie的源为https://study.duyiedu.com

http://localhost:7001/index.html的源为http://localhost:7001

两个URL地址的源完全相同 ,则称之为同源 ,否则称之为异源(跨域)

跨域出现的场景

跨域可能出现在三种场景:

  • 网络通信

    a元素的跳转;加载css、js、图片等;AJAX等等

  • JS API

    window.openwindow.parentiframe.contentWindow等等

  • 存储

    WebStorageIndexedDB等等

对于不同的跨域场景,以及每个场景中不同的跨域方式,同源策略都有不同的限制。

本文重点讨论网络通信AJAX的跨域问题

网络中的跨域

当浏览器运行页面后,会发出很多的网络请求,例如CSS、JS、图片、AJAX等等

请求页面的源称之为页面源 ,在该页面中发出的请求称之为目标源

当页面源和目标源一致时,则为同源请求 ,否则为异源请求(跨域请求)

浏览器如何限制异源请求?

浏览器出于多方面的考量,制定了非常繁杂的规则来限制各种跨域请求,但总体的原则非常简单:

  • 对标签发出的跨域请求轻微限制
  • 对AJAX发出的跨域请求严厉限制

解决方案

CORS

CORS(Cross-Origin Resource Sharing)是最正统的跨域解决方案,同时也是浏览器推荐的解决方案。

CORS是一套规则,用于帮助浏览器判断是否校验通过。

CORS的基本理念是:

  • 只要服务器明确表示允许 ,则校验通过
  • 服务器明确拒绝或没有表示,则校验不通过

所以,使用CORS解决跨域,必须要保证服务器是「自己人」

请求分类

CORS将请求分为两类:简单请求和预检请求。

对不同种类的请求它的规则有所区别。

所以要理解CORS,首先要理解它是如何划分请求的。

简单请求

完整判定逻辑:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests

简单来说,只要全部满足下列条件,就是简单请求:

  • 请求方法是GETPOSTHEAD之一

  • 头部字段满足CORS安全规范,详见 W3C

    浏览器默认自带的头部字段都是满足安全规范的,只要开发者不改动和新增头部,就不会打破此条规则

  • 如果有Content-Type,必须是下列值中的一个

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
预检请求(preflight)

只要不是简单请求,均为预检请求

练习
js 复制代码
// 下面的跨域请求哪些是简单请求,哪些是预检请求

// 1
fetch('https://douyin.com');

// 2
fetch('https://douyin.com', {
  headers: {
    a: 1,
  },
});

// 3
fetch('https://douyin.com', {
  method: 'POST',
  body: JSON.stringify({ a: 1, b: 2 }),
});

// 4
fetch('https://douyin.com', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
  },
  body: JSON.stringify({ a: 1, b: 2 }),
});
对简单请求的验证
对预检请求的验证
  1. 发送预检请求
  1. 发送真实请求(和简单请求一致)
细节1 - 关于cookie

默认情况下,ajax的跨域请求并不会附带cookie,这样一来,某些需要权限的操作就无法进行

不过可以通过简单的配置就可以实现附带cookie

js 复制代码
// xhr
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

// fetch api
fetch(url, {
  credentials: "include"
})

这样一来,该跨域的ajax请求就是一个附带身份凭证的请求

当一个请求需要附带cookie时,无论它是简单请求,还是预检请求,都会在请求头中添加cookie字段

而服务器响应时,需要明确告知客户端:服务器允许这样的凭据

告知的方式也非常的简单,只需要在响应头中添加:Access-Control-Allow-Credentials: true即可

对于一个附带身份凭证的请求,若服务器没有明确告知,浏览器仍然视为跨域被拒绝。

另外要特别注意的是:对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为*。这就是为什么不推荐使用*的原因

细节2 - 关于跨域获取响应头

在跨域访问时,JS只能拿到一些最基本的响应头,如:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。

Access-Control-Expose-Headers头让服务器把允许浏览器访问的头放入白名单,例如:

复制代码
Access-Control-Expose-Headers: authorization, a, b

这样JS就能够访问指定的响应头了。

JSONP

在很久很久以前...并没有CORS方案

在那个年代,古人靠着非凡的智慧来解决这一问题

虽然可以解决问题,但JSONP有着明显的缺陷:

  • 仅能使用GET请求

  • 容易产生安全隐患

    恶意攻击者可能利用callback=恶意函数的方式实现XSS攻击

  • 容易被非法站点恶意调用

因此,除非是某些特殊的原因,否则永远不应该使用JSONP

代理

CORS和JSONP均要求服务器是「自己人」

那如果不是呢?

那就找一个中间人(代理)

比如,前端小王想要请求获取王者荣耀英雄数据,但直接请求腾讯服务器会造成跨域

由于腾讯服务器不是「自己人」,小王决定用代理解决

如何选择

最重要的,是要保持生产环境和开发环境一致

下面是一张决策图

具体的几种场景


相关推荐
打小就很皮...6 天前
浏览器存储 Cookie,Local Storage和Session Storage
前端·缓存·浏览器
小妖6668 天前
chrome 浏览器怎么不自动提示是否翻译网站
浏览器
大名人儿12 天前
【浏览器网络请求全过程】
浏览器·网络请求·详解·全过程
windliang13 天前
Cursor 写一个网页标题重命名的浏览器插件
前端·浏览器
前端付豪13 天前
1、为什么浏览器要有渲染流程? ——带你一口气吃透 Critical Rendering Path
前端·后端·浏览器
啵啵学习14 天前
浏览器插件,提示:此扩展程序未遵循 Chrome 扩展程序的最佳实践,因此已无法再使用
前端·chrome·浏览器·插件·破解
前端南玖15 天前
通过performance面板验证浏览器资源加载与渲染机制
前端·面试·浏览器
mx95117 天前
真实业务场景:在React中使用Web Worker实现HTML导出PDF的性能优化实践
性能优化·浏览器
前端南玖21 天前
浏览器如何确定最终的CSS属性值?解析计算优先级与规则
前端·css·浏览器
NSJim21 天前
微软Edge浏览器字体设置
edge·浏览器·字体设置