内容简介:首先欢迎大家关注我的跨域问题在Web开发中一直都是一个非常常见的问题,但在日常工作说实话使用频率并没有那么多,再加上重使用轻原理导致我对跨域问题理解的一直非常的浅显,借此机会让我们好好探索一下。跨域问题的起源于众所周知的浏览器同源策略。作为如今Web安全基石的同源策略,早在上个世纪九十年代由网景公司提出,当时仅仅只是针对于
首先欢迎大家关注我的 Github博客 ,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励。最近工作上事情比较繁多导致博客一度断更,为了挽救惨淡的关注量并兼顾自己有限的时间,准备近期多推出一些有关前端的基础知识学习,一起夯实基础。希望大家多多关注呀!
引子
跨域问题在Web开发中一直都是一个非常常见的问题,但在日常工作说实话使用频率并没有那么多,再加上重使用轻原理导致我对跨域问题理解的一直非常的浅显,借此机会让我们好好探索一下。
同源策略
跨域问题的起源于众所周知的浏览器同源策略。作为如今Web安全基石的同源策略,早在上个世纪九十年代由网景公司提出,当时仅仅只是针对于 Cookie
的访问,即不同源的网页之间 Cookie
不能共享。后来同源策略变得更加严格,安全性也逐步提高,升级为:
- 不同源域 Cookie、LocalStorage 和 IndexDB 无法读取。
- 不同源域 DOM 无法获得。
- 不同源域 AJAX 请求不能发送
说了这么多同源,那么何为 同源 。通常情况下,如果一个源拥有相同的协议(protocol)、主机(host)、端口(port),则称其为同源。当然我们说了通常情况下,也就意味着这个方面有例外存在,IE毫无疑问的在这个方面承担了它固有的职责。IE并没有将端口号加入到同源策略的组成部分,因此 http://host:81
和 http://host:80
是属于同源的。虽然同源的安全限制是必要的,但是同时也带了束缚,假设我确实需要向不同源的地址发送请求该怎么办呢?那就回到了我们所要讨论的跨域问题。
跨域解决方案
JSONP
每每提起跨域问题,第一个想到的就是JSONP了。JSONP与JSON虽然看起来很像,但却有本质区别 。JSONP全称是JSON with Padding,并不是一种新的数据格式,而是数据格式JSON的一种“使用模式”。浏览器的同源限制对含有 src
属性的元素(例如: script
、 img
)不起作用,JSONP就是利用了 script
的这个特性。
基本原理
前面我们说了JSONP利用的是 script
标签,绕过浏览器的同源策略,试想我们通过一个URL获取JSON数据是可能的。然而JSON数据却是不可执行的,因此JSONP通过将返回的JSON数据用函数包裹来实现执行的目的,这也就是JSONP中P表示padding(包裹)的含义。因为需要函数包裹数据,所以前后端需要提前约定好函数名,我们一般会在请求的URL中带上函数名称作为参数。例如请求接口:
http://api.demo.com/data?userid=1&jsonp=parseResponse 复制代码
服务器会将对应的数据填充到函数 parseResponse
:
parseResponse({"Name": "小明", "Id" : 1823, "Rank": 7}) 复制代码
概念实现
我们实现一个最简单的函数用来创建一个JSONP请求:
function createJSONPRequest(url, callbackName){ var script = document.createElement("script"); script.src = url +"?callback=handleResponse"; document.body.appendChild(script); } function handleResponse(res){ console.log(res); } createJSONPRequest("http://localhost:3001/jsonp", "parseResponse") 复制代码
然后我们用express.js来响应这个JSONP请求:
// 服务器端口号:3001 app.get('/jsonp', (req, res) => { var callbackName = req.query.callback; var mockData = { name: "MrErHu" }; res.send(`${callbackName}(${JSON.stringify(mockData)})`); }) 复制代码
运行时你会发现浏览器会正确打印出跨域访问的数据,说明我们的跨域请求成功。
实践
当然我们并不需要在前端手动去实现该函数,很多第三方库已经封装了JSONP的请求,我们以JQuery提供的Ajax理由为例,上面的请求用JQuery去实现代码是:
// 客户端端口号:3000 $.ajax({ url: "http://localhost:3001/jsonp", type: "GET", dataType: "jsonp", jsonpCallback: "parseResponse", success: function (res) { console.log(res); } }); 复制代码
我们可以看到在JQuery中提供的 ajax
函数中通过配置 dataType
为 jsonp
,则可以实现JSONP请求,需要注意的是JQuery只是将其封装在 ajax
函数内,二者实质上有本质区别。
了解JSONP的实现原理,我们知道这玩意肯定不会存在浏览器兼容性问题,毕竟我不相信会存在不支持 script
标签的浏览器。但是因为正是通过 script
标签实现的,JSONP也就只能支持 GET
请求,那么如果我们想实现 POST
请求的跨域有什么好的办法吗?
CORS
CORS是Cross-origin resource sharing的缩写,即跨域资源共享,属于W3C标准,允许跨域发送 XMLHttpRequest
请求,支持多种HTTP Method。与JSONP的原理不同的是,CORS采用的是前后端HTTP表头协商的方式(我自己起的名字)判断是否允许跨域访问,并且相比与JSONP来说,CORS需要客户端和服务端共同支持,并且整个过程由浏览器自动完成。对于前端开发者而言,跨域的CORS通信与普通的Ajax通信没有差别,整个跨域处理的过程主要集中在服务器端。CORS通信分成简单请求(simple request)和非简单请求(not-so-simple request)两种模式。
简单请求
所谓的简单请求是指,请求方法属于: HEAD
、 GET
、 POST
之一,并且HTTP的头信息不超出以下几种字段:
Accept Accept-Language Content-Language Last-Event-ID Content-Type:仅限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain 复制代码
如果不满足以上任一条件,均属于非简单请求。对于简单请求,浏览器会直接发送CORS请求。当我们使用Ajax发送一条跨域请求,浏览器会在HTTP请求头(Request Headers)中增加 Origin
属性:
Origin
属性用来表明当前的请求来自于哪个源(协议、主机、端口)。如果服务端支持CORS跨域,则需要在响应头中返回以下属性:
Access-Control-Allow-Origin: Access-Control-Allow-Credentials Access-Control-Expose-Headers 复制代码
-
Access-Control-Allow-Origin
: 表示跨域的访问的源,其中"*"表示允许来自任何源的请求跨域访问 -
Access-Control-Allow-Credentials
: 表示跨域访问是否允许带有Cookie信息,该值仅可以允许设置为true
。如果服务器不允许请求携带Cookie,则不需要发送该属性。值得注意的是,携带的Cookie信息仅仅只是所跨域的服务器域名设置的Cookie,不能携带其他域名下的Cookie信息,Cookie仍然循序同源策略。并且还需要满足Access-Control-Allow-Origin
指定的域与当前的请求的域完全一致(不能是"*")以及在XMLHttpRequest显式设置withCredentials
属性为true
,表示浏览器也允许发送Cookie。 -
Access-Control-Expose-Headers
: 该字段可选,表示浏览器可以从该XMLHttpRequest
请求中拿到的响应头信息。默认仅能拿到Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
这六个属性,如果想取得其他的属性,必须在该属性中指定。
我们用 express.js
模拟一下:
// 服务器端口号:3001 app.post('/cors', (req, res) => { var mockData = { name: "MrErHu" }; var origin = req.get("origin"); res.set("Access-Control-Allow-Origin", origin); res.set("Access-Control-Allow-Credentials", true); res.send(JSON.stringify(mockData)) }); 复制代码
当我们前端Ajax请求:
// 浏览器端口号:3000 $.ajax({ url: "http://localhost:3001/cors", type: "POST", success: function (res) { console.log(res); } }); 复制代码
你会发现该条请求已经不会出现跨域问题,可以正确访问到数据。
非简单请求
对于非简单请求,浏览器会预先发送一次预检请求,询问服务器是否允许跨域访问以及是否允许HTTP方法,只有得到肯定答复后,浏览器才会发出正式的HTTP请求。比如我们跨域向服务器发送一次 PUT
请求:
// 浏览器端口号:3000 $.ajax({ url: "http://localhost:3001/cors", type: "PUT", success: function (res) { console.log(res); } }); 复制代码
你会发现浏览器首先会发送OPTIONS请求去预检本次跨域请求。
当预检请求检测了 Origin
、 Access-Control-Request-Method
和 Access-Control-Request-Headers
属性后就会正式发送请求。需要注意的是,不仅仅是上述三个属性,预检请求还会返回 Access-Control-Max-Age
属性用来表示该条预检请求的有效期,在有效期内,浏览器不会再次发送预检请求,仅会使用该条缓存的预检请求。
我们用 express.js
来模拟本次请求:
// 服务器端口号:3001 app.options('/cors', (req, res) => { var origin = req.get("origin"); res.set("Access-Control-Allow-Origin", origin); res.set("Access-Control-Allow-Credentials", true); res.set("Access-Control-Allow-Methods", "PUT"); res.set("Access-Control-Max-Age", 24 * 60 * 60 * 1000); res.send(); }); app.put('/cors', (req, res) => { var mockData = { name: "MrErHu" }; var origin = req.get("origin"); res.set("Access-Control-Allow-Origin", origin); res.set("Access-Control-Allow-Credentials", true); res.send(JSON.stringify(mockData)) }); 复制代码
你就会发现本次 PUT
跨域请求成功,正确返回数据,达到了我们跨域的要求。
对比
与JSONP相比,CORS支持更多的HTTP方法并且整个过程由浏览器自动完成,但是仅仅只兼容IE10及以上的浏览器,如果需要兼容老版本IE浏览器,CORS可能就不适合你了。
后言
我们这边文章着重讲述了两种跨域的基本原理,但实际上围绕跨域问题还有很多值得学习的地方,比如后端对跨域请求的处理,以及前端跨域带来种种的安全性问题。其实不仅仅是JSONP与CORS,Websocket也能实现跨域请求,以后有机会可以学习一下,最后还是欢迎大家关注我的 博客 ,水平欠佳,希望体谅,愿一同进步。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Spring源码探究:容器
- Flutter BuildContext 探究
- Flutter mixins 探究
- Serverless 一些探究(一)
- Innodb锁探究
- ConcurrentHashMap探究
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
编程的修炼(中英双语)
[荷]Edsger W. Dijkstra / 裘宗燕 / 电子工业出版社 / 2013-7 / 79.00元
本书是图灵奖获得者Edsger W. Dijkstra在编程领域里的经典著作中的经典。作者基于其敏锐的洞察力和长期的实际编程经验,对基本顺序程序的描述和开发中的许多关键问题做了独到的总结和开发。书中讨论了顺序程序的本质特征、程序描述和对程序行为(正确性)的推理,并通过一系列从简单到复杂的程序的思考和开发范例,阐释了基于严格的逻辑推理开发正确可靠程序的过程。 本书写于20世纪70年代中后期,但......一起来看看 《编程的修炼(中英双语)》 这本书的介绍吧!