浅谈跨域
阅读须知: 作者是一个在校大学生,尚未工作,以下内容依据个人理解与网上资料编写。若有错误,还请指出,感激不尽。网上对于跨域的解释大多是一堆文字,对于初学者来说往往较难理解,这篇博客我将利用NodeJs搭建一个简易的服务器用于模拟跨域,不懂NodeJs的小伙伴也不用紧张,只是借助于NodeJs来快速搭建一个http服务器。我将以最简单的代码去还原跨域问题。若有兴趣的小伙伴,可自行百度学习(菜鸟教程)。本篇博文所有代码均已跑通且会上传至个人仓库。欢迎各位指正错误。
1.跨域是什么?
这就是一个跨域引起的错误。或许许多小伙伴,一遇到错误就直接百度寻找答案,但有时候我们不妨看看错误信息,这对我们理解问题可能会有不错的帮助,而不是遇到问题就百度,goole。我们来看看错误信息都告诉我们什么?
Access to XMLHttpRequest at 'http://127.0.0.1:8002/index.html' from origin 'http://127.0.0.1:8001' has been blocked by CORS policy: No'Access-Control-Allow-Origin' header is present on the requested resource.
我们把它丢到百度翻译可以的到如下解释:
我们把它理顺一下可以变成:
一个网页请求另外一个网页的资源被CORS策略给阻止了,原因XMLHttpRequest的请求不存在“Access-Control-Alloworigin"头,如果你恰巧有网络协议方面的知识储备,或许你已经了解跨域是什么了,以及如何解决。
我们可以在简化一下这个信息:网页间的XMLHttpRequest请求被CORS策略给阻止了。好的,来到这里我们已经知道了跨域请求失败的原因是什么了。CORS策略阻止了它!!
CORS是什么?
(推荐阅读MDN的文章:中文版:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS ,英文版:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)
CORS:Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.
由上边的介绍可知:CORS是不同域之间资源共享的一种机制,它使用额外的Http头来告诉浏览器让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
跨域的安全限制,主要是针对浏览器端来说的,服务器端是不存在跨域安全限制的。。所以我们可以通过代理服务器去解决跨域问题。
域:简单理解由协议+域名+端口组成
譬如文章开头图片中的两个域http://127.0.0.1:8002/index.html与http://127.0.0.1:8001是由于端口号不同而造成了跨域。http是他们的协议,127.0.0.1是他们的Ip地址,这里是本机Ip。8001,8002是他们的端口号,简单点说ip地址是你的电脑在网络世界里的标识。(Ip不是唯一不变的,它随着你接入的网络可能会发生改变,每台电脑的网卡还有一个物理地址MAC地址。MAC地址用于在网络中唯一标示一个网卡,一台设备若有一或多个网卡,则每个网卡都需要并会有一个唯一的MAC地址,这里不过多赘述,以后或许会更新相关内容的博文)
2.跨域的原因是什么
虽然在上边的翻译中告诉我们是因为CORS策略给阻止了请求,但我们通过查阅CORS在MDN上的解释可以知道,CORS是不同域之间共享资源的一种机制,也就是说它可以说是解决跨域的一种方案。那么引起跨域的原因是什么?
上面说到跨域只存在于浏览器之间,这是因为浏览器存在有一个同源策略。什么是同源策略(SOP)呢?可以看一看MDN上的文章(https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy)。
同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。所以xyz.com下的js脚本采用ajax读取abc.com里面的文件数据是会被拒绝的。同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
阮一峰的博客中有一段跨域同源策略的描述
同源政策的目的:是为了保证用户信息的安全,防止恶意的网站窃取数据。
设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的Cookie,会发生什么?很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。由此可见,"同源政策"是必需的,否则Cookie 可以共享,互联网就毫无安全可言了。
在这里简单介绍一下cookie,因为理解了cookie能更好的理解浏览器与服务器间的交互。我们知道浏览器与服务器间使用过http协议来进行交互的,但是HTTP协议是无状态的,服务器是无法通过接收到的HTTP请求来区分是哪个用户发起的请求,即服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现。在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两饮料。最后结帐时,由于HTTP的无状态性,不通过额外的手段,服务器并不知道用户到底买了什么。为了做到这点,就需要使用到Cookie了。服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。
如何查看Cookie?
可以打开菜鸟教程里有关cookie的介绍https://www.runoob.com/js/js-cookies.html
各位可以发现Cookies是有分组的,每个域下边存在有不同的cookie值,Cookie是由服务端生成的,发送给客户端(通常是浏览器)的。Cookie总是保存在浏览器中。
当浏览器与服务器之间通信时,浏览器会在每个http请求中携带当前域下的cookie。
到这里我们已经大概了解什么是同源策略,为什么要有同源策略。
同源策略限制的行为:
(1) 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。
(2) 无法接触非同源网页的 DOM。
(3) 无法向非同源地址发送AJAX请求(可以发送,但浏览器会拒绝接受响应)。
不受同源策略限制的情况:
有一些情况是不受同源策略的影响,简单列举如下:
(1)页面中的超链接,点击可以访问其他不同源的页面。
(2)表单提交,不同源的页面可以相互提交表单数据。
(3)通过Html标签请求资源,如scrpit,img标签。
3.解决跨域
这里介绍三中种跨域的解决方案 JSONP,代理服务器和CORS。
JSONP
JSONP利用的是通过HTML标签请求资源,不受同源策略影响。它的基本思想是,网页通过添加一个script元素,向服务器请求数据,这种做法不受同源策略限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。 先贴上一段网上摘抄的代码
# 预先定义好处理数据的回调
function CallbackName(data) {
console.log('Your public IP address is: ' + data.ip);
};
# 创建scrpit元素请求资源,以备后用
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
# 在页面加载完成时调用函数发送指定目标请求
window.onload = function () {
addScriptTag('http://example.com/ip?callback=CallbackName');
}
初次看这代码时不太理解,因为当时没有接触后端开发,无法理解它们的交互过程。我们要明白一点script标签请求回来的数据浏览器是当成JS去解析的,我们可以简单验证一下,虽然没有资料讲述这一部分的内容。
script1.js代码如下
<h1>11111</h1>
script2.js代码如下
alert(1);
当把这个页面打开,会发现页面弹出两次弹框,控制台提示无法解析h1标签的错误。所以可以大概理解浏览器是将请求回来的数据当成js代码执行。
这时候我们就可以通过URL所带的queryString传递我们的函数名,例如下边这个URL传递了CallbackName作为回调函数的名称,而原先的JS代码又预先定义了回调函数。当后代给我们返回,Callbackname({dataObj})这样的字符串时,浏览器就会自动调用预先定义的回调函数,而需要的数据就通过{dataObj}传递过来了,于是实现了跨域请求。还是不太理解的小伙伴可以去我的仓库里查看相关代码。
http://example.com/ip?callback=CallbackName
JSONP只能支持get请求,并且前后端需要商量好回调函数的名称。