深入跨域,理论和实践
讲一下跨域是什么
一个源加载文档或者脚本和来自另一个源的文档和脚本等资源进行交互(也就是不满足同源策略的两个源之间进行一些交互),就是跨域
2.同源策略
2.1同源策略是什么
所谓的同源指的是"三个相同”
- 协议相同
- 域名相同
- 端口相同
举个例子:
http://www.wuhuiluo.com/dir/page.html
这个而网址,协议是http://
,域名是www.wuhuiluo.com
,端口是80(默认端口可以省略),来看看下面改变的哪些是同源哪些不是同源
http://www.wuhuiluo.com/dir2/other.html
: 同源http://www.wuhuiluo1.com/dir/other.html
: 不同源(域名不同)http://v2.www.baidu.com/dir/other.html
: 不同源(域名不同)http://www.baidu.com:81/dir/other.html
: 不同源(端口不同)https://www.wuhuiluo/dir/page.html
: 不同元(协议不同)
2.2 为什么需要同源策略
同源策略的目的是为了保证用户的信息安全,防止恶意的网站窃取数据,它能帮助阻隔恶意文档,减少可能被攻击的媒介,假设小明同在A银行的官网进行了登陆,之后它又去浏览了其他网站,如果其他网站可以读取A银行官网的cookie,那么小明在A银行的登陆信息和其他存款记录都会被泄露,将是一件非常危险的事情。
2.3 同源策略带来了什么访问限制
-
跨源数据存储访问: 范文存储在浏览器中的数据,如localStorage和indexDB,是以源进行分割,Cookies使用不同的源定义方式,每个源都拥有自己单独的存储空间,一个源中的JavaScript脚本不能对属于其他源的数据进行读写操作
-
跨源脚本API访问: JavaScript的API中,如iframe.contentWindow,window.parent,window.open和window.opener允许文档间直接相互引用,当两个文档的源不同时,这些引用方式将对 Window 和 Location对象的访问添加限制
-
跨源网络访问: 同源策略控制不同源之间的交互,例如在使用XMLHttpRequest 或 标签时会受到同源策略的约束
3.解决跨域的几种方法
将上面三种访问限制简化成下面的三种表达
(1) Cookie、LocalStorage 和 indexDB 无法获取
(2) Javascript的API中的一些引用,无法获取(详见上)
(3) AJAX请求不能发送 (也就是无法使用XMLHttpRequest)
3.1 cookie – document.domain
当我们尝试解决因同源策略下,无法访问cookie这种情况时,我们可以借助:
- 1.
浏览器允许通过设置document.domain共享Cookie
,来达成效果,但是两个网页一级域名相同,知识二级域名不同才可以设置
,那什么是一级域名,什么是二级域名呢?
举个例子: A网页: http://w1.wuhuiluo.com/a.html
在这个网页地址中,w1.wuhuiluo.com
这个部分统称为域名
-
一级域名是由一个合法的字符串+域名后缀组成,所以,wuhuiluo.com这种形式的域名才是一级与i摩纳哥,wuhuiluo是域名主体,.com,.net是域名后缀
-
二级域名实际上就是一级域名下面的主机名,顾名思意:它是在一级赢前面加上一个字符串, 比如w1.wuhuiluo.com
解释完怎么样的情况才可以设置document.domain共享Cookie,让我们看看如何操作:
假设有两个网页地址,我们可以看到,他们的一级域名是相同的,二级域名是不同的
A网页: http://w1.wuhuiluo.com/a.html
B网页: jttp://w2.wuhuiluo.com/b.html
那么只要设置相同的document.domain,两个网页就可以共享Cookie
document.domain = 'example.com'
A网页通过脚本设置一个Cookie
document.cookie = 'test1=hello
B网页就可以读取到这个Cookie
var allCookie = document.cookie
2.服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,比如example.com
Set-Cookie: key-value; domain=.example.com; path=/
这样的话么日记域名和三级域名不用做任何设置,都可以读取到这个Cookie
这里的话,补充一下设置cookie的时候,一些其他的设置来限定其可访问性:
- Domain 和 Path 标识定义了Cookie的作用域,即允许Cookie应该发送给那些URL
- Secure: Secure属性是说如果一个cookie被设置了Secure=true,那么这个cookie
只能用https协议发送给服务器,用http协议是不发送的。 - HttpOnly: 使用HttpOnly 属性可以防止通过 JavaScript访问cookie值
- SameSite Cookie允许服务i去要求某个cookie在跨站请求时不会被发送,从而可以阻止跨站请求伪造攻击(CSRF)
这里我们只是单单解决了在有一些限制条件下的访问cookie
的限制,但是上面讲到的
LocalStorage
和indexDB
暂时还没有解决.
3.2 API访问 – window.postMessage
postMessage是html5新增的一个解决跨域的方法,为了能让不同源中文档进行交流,跨域使用window.postMessage安全地实现跨域通信(安全是在正确使用的情况下)
3.2.1
举个例子
两个窗口页面之间的通信,因此我们这边假设两个页面A,B,目的是在B窗口点击postMessage按钮,能够在A页面收到发来的消息,A页面:
<script>
function test() {
let op = window.open('b.html', '_blank');
function receiveMessage(event) {
console.log('event', event);
}
op.addEventListener("message", receiveMessage, false);
}
</script>
<body>
<div>
<button onClick="test()">open</button>
</div>
</body>
B页面:
<script>
function post() {
window.postMessage("hi there!", location.origin);
}
function receiveMessage(event) {
console.log('event', event)
}
window.addEventListener("message", receiveMessage, false);
</script>
<body>
<div>
<button onClick="post()">postMessage</button>
</div>
</body>
说一下大概的思路:先看B页面
- 在B页面中有一个按钮,点击这个按钮会触发一个方法,post()
- 在post()方法中,window.postMessage(‘hi there’,location.origin),发送到所有同源的窗口,注意,当前的窗口也会收到
- 之后通过window.addEvenetListener(‘message’,receiveMessage,false)去监听,
如果有数据,就执行receiveMesage()把数据打印出来
再来看A页面 :
- 在A页面也有一个按钮,当点击这个按钮时触发test()
- 打开新窗口,并建立窗口的引用变量 op = window.open(‘B.html’, ‘_blank’)
- op.addEventListener(‘message’,receiveMessage,false)监听新窗口发来的消息,通过receiveMessage()把数据打印出来
3.2.2 如何正确的使用,以保证安全性
-
始终使用origin和source属性验证发件人的身份,没有验证origin和source会导致跨站点脚本攻击
-
当使用postMessage将数据发送到其他窗时候,指定精确的目标origin,而不是*
3.3 JSONP
JSONP(JSON width Padding)是JSON的一种"使用模式",可用于解决主流浏览器的跨域数据访问的问题
3.3.1 JSONP的介绍
JSONP是通过在标签里,通过src,img,href属性的跨域方式向一个不同源的网站地址发送http请求,并且使得json数据可以在javascript代码中能够使用
它规避了javascirpt代码中的跨源网络访问,也就是无法使用XMLHttpRequest,fetch被同源机制管到了(如果不同源的话).
提前准备一个接口: https://photo.sina.cn/aj/index?page=1&cate=recommend
直接网页中打开,我们是可以看到有很多数据的,如下图:
让我们尝试在本地请求一下这个地址,看看能不能拿到数据,因为双方地址并不是同源的,因此这样请求会报跨域错
<body>
<script>
fetch('https://photo.sina.cn/aj/index?page=1&cate=recommend')
.then(res => {
console.log(res);
})
</script>
</body>
通过live-server打开浏览器,在控制台就可以看到报错了,因为这是一个跨域的请求
接下来我们使用JSONP来解决这个问题:
3.3.2 JSONP如何使用,原理是什么?返回数据格式?前端怎么处理?
还是请求上面的这个网站地址,我们把代码改成下面这样
<body>
<script>
function callback(data){
console.log(data);
}
</script>
<script src="https://photo.sina.cn/aj/index?page=1&cate=recommend&callback=callback"></script>
</body>
再来看看页面控制台的输出:
res成功取到了,但是我们的数据到达之后还是json数据,不能直接使用,script标签是一个加载资源的标签,它并不能直接运行这个代码
事实上我们是在访问的时候,在请求的地址后面加上一个,&callback=callback
,通知服务器,本地想进行一个跨资源访问(以JSONP的形式进行跨域)。等号后面的callback
是一个你自己定义的函数,名字可自取,这个函数是,通知我需要请求的地址,这边页面上我有一个函数,它会等待调用,用来执行你发过来的数据(也就是可以去执行把数据请求下来的操作)
因此在数据到达之后,还包了一层函数callback({data})
,当数据通过script请求下来之后,再通过callback
实现一个调用本地资源的能力
整理一下这部分的内容
- JSONP的原理
script标签请求数据,在请求的地址后面加上一个,&callback=callback
, 请求的服务器就可以在json数据外面包一层callback函数,当这个带有数据的callback函数可以在scirpt得到之后运行
的函数:
- 返回的数据格式
JSON
- 以及前端如何处理
JSON with padding — callback({data})
3.4 cors
3.4.1 介绍一下cors
》 CORS是一个W3C标准,全程是“跨域资源共享”,它允许浏览器向跨域服务器,发出XMLHttpRequest请求,从而客服了AJAX只能同源使用的限制
3.4.2 简单请求和非简单请求
浏览器将CORS请求分为两类: 简单请求和非简单请求
除了简单请求其他的都是非简单请求
简单请求: (需要同时满足下面的两种条件)
- 请求方法是下面三种
- HEAD
- GET
- POST
- HTTP的头信息不超出以下几种字段:
- Accept: 设置接受的内容类型(请求头)
- Accept-Language: 设置接受的语言(请求头)
- Content-Language: 为封闭内容设置自然语言或者目标用户语言(响应头)
- Content-Type: (设置请求体的MIME类型(适用于POST和PUT请求))只限于三个值
application/x-www-form-urlencoded
:
默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)
multipart;/form-data
: 将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件。
text/plain
: text/plain: 纯文本格式
3.5 代理(nginx)
3.5.1 原理
A网站向B网站请求1.js文件,向B网站发送一个获取的请求,nginx根据配置文件接受这个请求,代替A网站向B网站来请求这个资源,nginx拿到这个资源吼在返回给A网站,以此来解决了跨域问题