共计 5015 个字符,预计需要花费 13 分钟才能阅读完成。
跨域是前端开发中一个十分常见的问题,尤其是随着 单页利用(Single Page Application, SPA)的衰亡,前后端拆散开发和部署,前端在本地开发和部署的过程中都会面临着跨域问题。咱们再次聊聊跨域这个话题,以及我的项目中对跨域的一些实践经验,心愿带来一些新的播种。
什么是跨域
首先咱们须要理解下什么是 同源策略,MDN 中是这样介绍的:
同源策略是浏览器的一个重要安全策略,它用于限度一个源的文档或者它加载的脚本如何能与另一个源的资源进行操作。它能帮忙阻隔歹意文档,缩小可能被攻打的媒介。
从这段话咱们得悉,同源策略是 浏览器 的平安 策略,源 (Origin)是判断是否满足同源策略的条件。咱们常说的跨域,精确来说应该是 跨源,目标是不受同源策略的限度去操作另一个源的资源。例如当咱们在本地开发时,源是 http://localhost:3000
,而服务端接口的地址是 https://api.feishu.cn
,此时通过 Fetch API
拜访接口就会产生跨域。
同源策略源自于浏览器,反之在非浏览器的环境下,个别是没有同源策略限度的。例如在 Node.js 中,咱们能够申请任意的网址并失去后果。因而基于 Node.js 的服务端能够间接跨域拜访资源。
当然咱们也能够敞开浏览器的同源策略,敞开后浏览器环境下也能够间接跨域。以 Chrome 为例,通过给 Chrome 减少启动参数:
$ /path/to/chrome.app --disable-web-security
即可敞开。敞开后浏览器会有平安危险,倡议只用在本地开发上
源由 协定 、 域名 (精确来说是 主机名 ,因为除了域名也能够是 IP)、 端口号 独特决定,三者完全相同的两个 URL 会被认为是同源。举几个例子:
https://www.bytedance.com
和https://jobs.bytedance.com
,域名不同,不同源http://www.bytedance.com
和https://www.bytedance.com
,协定不同,不同源http://localhost
和http://localhost:8080
,端口不同(80 和 8080),不同源
特地的,当咱们间接关上一个 HTML 文件时,应用的是 file
协定,申请 HTTP 资源时协定不同,会产生跨域。
源是容许被有限度的批改,浏览器提供了 API 能够将子域名下的源批改为父域名的源,例如咱们在 https://open.feishu.cn
下执行:
document.domain = 'feishu.cn';
此时页面的源就变成了 https://feishu.cn
满足同源策略,咱们就能够间接拜访父域名下的资源。如果批改为非父域名(如 bytedance.com
),浏览器会报错。
同源策略管制不同源之间的操作,这些操作通常分为三类:
- 资源嵌入:个别是被容许的。例如
<script>
、<iframe>
引入资源 - 写操作:个别是被容许的。例如链接、重定向以及表单提交,满足特定条件的 HTTP 申请不容许
- 读操作:个别是不被容许的。例如
XMLHttpRequest
和Fetch API
发动 GET 申请
依据这个分类,咱们来重点探讨几种状况:
<script>
是资源嵌入不受同源策略管制,JSONP 就是借助这一个性实现的跨域<form>
是资源写入不受同源策略管制,因而表单的action
不是同源 URL 时也能够提交胜利-
XMLHttpRequest
或Fetch API
是咱们重点关注的,上面咱们具体探讨下:- 当发动 HTTP
GET
申请(也能够是其它类型)读取资源,受同源策略管制,申请返回的内容在不同源的状况下咱们是读不到的 - 当发动 HTTP
POST
申请批改资源,个别是不受同源策略管制的(称为简略申请),资源是容许被批改;而对于非简略申请,受同源策略管制,资源不容许批改
- 当发动 HTTP
须要特地阐明的是:
- HTTP
GET
读取资源尽管受同源策略管制,但申请是胜利发送的,只是浏览器限度了申请返回的内容不给咱们而是抛出了谬误 - HTTP
POST
写入资源时,简略申请也是发送胜利的,因而资源被胜利批改了,因而能够说写操作不受同源策略管制,但申请的返回值咱们同样获取不到(因为是读操作) - 非简略申请采纳了先发一个预检申请的形式,判断是否容许跨域,不容许则不会发送实在的申请,防止资源被批改,因而受同源策略管制。上面实际局部会具体探讨如何容许跨域。
WebSocket
不受同源策略管制
简略申请必须满足以下条件:
- HTTP 办法是:
GET
POST
或HEAD
-
除了被浏览器主动设置的字段(例如
Connection
、User-Agent
),申请头只容许以下字段:Accept
Accept-Language
Content-Language
Content-Type
:只允许值为text/plain
multipart/form-data
application/x-www-form-urlencoded
Range
:只容许简略的范畴标头值,如bytes=256-
或bytes=127-255
不满足上述条件的即为非简略申请。例如当咱们在申请头减少 X-JWT-Token
或 Content-Type: application/json
时,这个申请就是非简略申请。
为什么须要同源策略
下面咱们说了,同源策略是一个安全策略。如果同源策略被敞开,个人信息将会有平安危险,例如:
- 歹意站点能够调用其它网站的用户信息接口,获取敏感信息
- 歹意站点能够调用其它网站的点赞或删除接口,歹意批改资源
跨域的最佳实际
那么咱们如何能力跨域呢?浏览器在同源策略的根底上,提出一种能够平安的跨域机制称为 跨源资源共享(Cross Origin Resource Sharing,CORS)。当然在这个机制公布之前,也有 JSONP
等满足浏览器平安机制的跨域计划,本文不具体探讨。
CORS 的应用很简略,服务端(即被申请的资源)在响应头中减少:
Access-Control-Allow-Origin: https://www.bytedance.com
即可,其中的值示意容许跨域拜访的源,此时 https://www.bytedance.com
就能够拜访这个服务器的资源。咱们能够看出,申请是胜利发送并失去响应的,浏览器能力拿到 Access-Control-Allow-Origin
响应头并判断是否能够跨域(先申请再判断)。那么对于非简略申请,为了防止资源被意外批改,是须要先判断是否能够跨域再发动批改申请的(先判断再申请),这时浏览器就会先发动一个预检申请(HTTP 办法为 OPTIONS
),拿到服务端返回的 Access-Control-Allow-Origin
并判断,满足容许跨域的话再发动实在申请。
如果咱们无奈批改服务端呢?能够实现一个咱们可控的代理服务端,减少容许跨域响应头或间接同域,解决跨域问题,例如通过 Nginx
、Charles
或 webpack-dev-server
代理。
上面咱们来探讨我的项目中几种常见的跨域场景和最佳实际:
跨域申请须要携带 Cookie
XMLHttpRequest
和 Fetch API
在发动跨域申请时默认是不携带服务端所在源的 Cookie,这样影响到了服务端的用户身份甄别。咱们须要减少参数,例如:
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
或
fetch(url, {credentials: 'include',});
浏览器才会携带 Cookie,同时服务端须要返回响应头:
Access-Control-Allow-Credentials: true
浏览器才会失常将响应内容返回给咱们,否则会抛出谬误
容许白名单内的源跨域申请
当有多个源须要容许跨域拜访时,服务端能够配置
Access-Control-Allow-Origin: *
容许所有源跨域拜访,但凋谢范畴太大有肯定的安全隐患,同时这种状况下浏览器不容许携带 Cookie。咱们须要对源精细化的管制,但 Access-Control-Allow-Origin
不容许设置多个源。咱们能够通过申请头 Origin
加白名单判断的形式,动静返回 Access-Control-Allow-Origin
的值解决。
Origin
是跨域申请时浏览器主动携带的值,示意申请的源,咱们在服务端定义一个白名单判断这个源是否在白名单中,如果在则返回 Access-Control-Allow-Origin
的值等于申请头的 Origin
。以 express
为例代码如下:
const whiteList = ['https://jobs.bytedance.com', 'https://www.bytedance.com'];
app.get('/path/to/api', (request, response) => {const { origin} = request.headers;
if (whiteList.includes(origin)) {response.header('Access-Control-Allow-Origin', origin);
response.header('Access-Control-Allow-Credentials', true);
}
});
获取跨域申请返回的某些响应头
跨域响应中的响应头默认并不是所有的都能够被咱们获取到,默认浏览器只返回一些根本响应头,包含:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
当咱们须要获取一些额定的响应头,例如 X-TT-LogID
用于记录每次申请的 logID
,咱们须要让服务端返回响应头:
Access-Control-Expose-Headers: X-TT-LogID
才能够获取到
获取资源应用简略申请
后面咱们理解到,非简略申请会先发动预检申请以查看是否容许跨域,因而须要两次 HTTP 申请能力实现这次操作。对于咱们已知是读操作的申请,咱们能够尽量满足简略申请的条件以缩小预检申请,从而缩小申请工夫。
一般来说,咱们须要留神的中央如下:
- 读取资源时尽量应用
GET
或POST
申请 - 对于
POST
申请且须要通过 body 传递 JSON 数据时,Content-Type
应用text/plain
而不是application/json
,同时服务端也须要反对将text/plain
解析成 JSON - 对于其它须要申请携带的信息,尽量放在申请参数中而不是自定义申请头,因为额定的申请头也会导致变成非简略申请,例如 JWT 的
X-JWT-Token
CDN 资源也须要容许跨域
如果咱们接入了 Sentry
或 Perfsee
等前端监控平台并须要监控站点的 JS 脚本谬误,且这些 JS 脚本是部署在 CDN 服务器上,因为 CDN 和咱们的站点往往不同源,浏览器对于不同源的 <script>
资源嵌入不会返回具体的错误信息(读取错误信息属于读操作受同源策略管制),这样监控平台收集到的错误信息不残缺,对定位问题带来了负面影响。
咱们须要让 CDN 服务器也返回 Access-Control-Allow-Origin
头容许咱们的源,同时所有引入 JS 的 <script>
须要减少 crossorigin
属性:
<script src="/path/to/cdn" crossorigin="anonymous"></script>
总结
咱们从三个角度探讨了跨域是什么、为什么须要同源策略以及跨域的解决方案和实际,重点总结如下:
- 同源策略是浏览器的安全策略,Node.js 环境没有跨域限度
- 浏览器的同源策略通过启动参数
disable-web-security
能够敞开 - 通过协定、域名、端口号三元组判断同源
- 通过
document.domain
能够有限度的批改源 - 简略申请与非简略申请的区别,非简略申请会先发动预检申请
- 同源策略保障了浏览器的安全性
- 跨域通过减少响应头
Access-Control-Allow-Origin
解决,也能够借助代理实现 -
跨域的一些最佳实际:
- 跨域申请通过
Access-Control-Allow-Credentials
携带 Cookie - 通过申请头
Origin
容许白名单内的源跨域申请 - 通过
Access-Control-Expose-Headers
获取跨域申请返回的某些响应头 - 获取资源尽量应用简略申请,以缩小申请耗时
- CDN 资源也须要容许跨域
- 跨域申请通过
如果有其它对于跨域的最佳实际,欢送分享