同源策略及跨域资源共享

作为一名前端,在日常开发的过程中我们使用ajax请求获取数据,我们常常会遇到跨域的问题,结合自己的实践和网上的资料,对浏览器的同源策略,解决跨域问题的常见方法进行总结。

同源策略

Q: 为什么我们需要同源?

A: 因为安全性的考虑。比如我们在登陆一个网站时留下的个人信息,证明身份的用户信息,登录的cookie信息,如果没有同源策略的限制别人就会很容易拿到这些信息,用户的安全性也就无法保障了。
Q: 怎么才算同源?

A: 协议相同 域名相同 端口相同

Q: 同源策略限制了哪些信息?

A: (0) Cookie、LocalStorage、IndexDB
Cookie表明了用户的身份 LocalStorage、IndexDB也可能包含用户的信息
(1) DOM 无法获得
DOM??? 为什么DOM也无法获得呢?
因为如果有一个伪装的网站在iframe中引入你真正要访问的网站 在子iframe中父级页面获得DOM元素后可以对ta进行事件的监听能够获得用户输入的密码等
(2) AJAX 请求无法发送
请求中的 cookie 会存储在浏览器客户端本地当 AJAX请求发出时会根据请求的域名带上 cookie 信息,如果没有同源策略限制就可以在恶意的页面中发起请求

源的更改

在我们需要在父域和子域之间进行正常的通信的时候,我们可以将父域和子域中的 document.domain 设置为父域,这样这两个页面会被当成同源。

如何绕过

同源策略对我们来说是必要的,但是有些合理的数据传递也被限制了,但是我们还是能够通过一些其他的手段来间接的传递数据

0.怎么做呢?

src属性具有天生跨域的能力 所以带有src属性的元素比如script iframe img 都不受同源策略的影响

1.JSONP

JSONP和JSON有什么关系?

JSON是自从AJAX2.0以来使用最广泛的数据传输格式 [] 表示 数组 {} 表示 对象
JSONP是一种数据的传输方式
所以说关系好像 JAVA 和 JavaScript(就是没啥关系)

那我们如何使用JSONP请求数据呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建一个script
var script = document.createElement('script')
document.body.appendChild(script)
// 前端接到数据后的处理函数
window.xxx = function (value) {
console.log(value)
}
// src设置为要请求的url在参数上传出给后端的参数 这个参数是你和后端规定好的可以是任意
script.src = 'http://x.stuq.com:7001/json?callback=xxx'
//在后端接到这个callback的值也就是你要执行处理数据的方法名
...
// 然后返回给前端
...
//

2.CORS

CORS 全称为跨域资源共享(cross-origin sharing standard)规定了在跨源资源访问时的规则
在跨源请求访问时会带上 Header 信息来让 server 端判断时候允许访问资源。

简单请求

同时满足下列所有条件的时候称为简单请求

  • 请求方法为 GET POST HEAD
  • Request header 在如下的列表中
    • Content-Type
    • Content-Language
    • Accept-Language
    • Accept
    • DPR
    • Downlink
    • Width
    • Viewport-Width
    • Save-Data
  • Content-Type 为下面三种类型之一
    • multipart/form-data
    • text/plain
    • application/x-www-form-urlencode
  • 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;
    XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用 ReadableStream 对象。

需要预检的请求

除简单请求外,请求都是需要预检的,在发出正常请求之前需要先发出一个 OPTIONS 请求,这个请求只携带少量的信息,来询问服务端是否允许实际请求。

Access-Control-Request-Methods
Access-Control-Request-Headers

Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Access-Control-Max-Age
Access-Control-Allow-Credentials

设置Access-Control-Allow-Origin
第一种.将你需要跨域的站点设置上’Access-Control-Allow-Origin’, ‘http://xx.stuq.com'
第二种.对所有站点的请求都不拦截’Access-Control-Allow-Origin’, ‘*’
这样设置之后就不用做任何事情了
参数还可以根据请求的站点动态设置,这样就保证了灵活性

3.location.hash

使用iframe 在iframe中加载一个与邀请同域的页面 在这个页面中发起请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 创建一个iframe
var iframe = document.createElement('iframe')
//加载一个同域的页面 我们在这个页面发ajax
iframe.src = 'http://x.stuq.com:7001/public/hash.html'
document.body.appendChild(iframe)
// 进入hash.html这个页面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
parent.location.href = `http://y.stuq.com:7001/public/3.html#msg=${res.msg}`
}
}
xhr.open('GET', 'http://x.stuq.com:7001/json', true)
xhr.send(null)
// 监听hash改变事件 取得信息
window.onhashchange = function () {
let hash = location.hash
let reg = /[#&][^#&]+=[^#&]+/g
let arr = hash.match(reg)
let obj = {}
arr.forEach(item => {
let tempArr = item.substring(1).split('=')
let key = decodeURIComponent(tempArr[0])
let val = decodeURIComponent(tempArr[1])
obj[key] = val
})
console.log(obj)
}

4.Window.name

开始也是与上面一样
不同的地方在于在iframe中请求到数据后如何带回来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 创建一个iframe
var iframe = document.createElement('iframe')
//加载一个同域的页面 我们在这个页面发ajax
iframe.src = 'http://x.stuq.com:7001/public/name.html'
document.body.appendChild(iframe)
var times = 0
// 监听iframe的onload事件当页面每两次加载时取数据
iframe.onload = function () {
if (++times === 2) {
console.log(JSON.parse(iframe.contentWindow.name))
}
}
// 进入name.html这个页面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
//使用window.name将数据保存
window.name = xhr.responseText
//跳回和parent页面同源的页面
location.href = 'http://y.stuq.com:7001/public/index.html'
}
}
xhr.open('GET', 'http://x.stuq.com:7001/json', true)
xhr.send(null)

5.HTML5 的psotmessage

开始也是与上面一样
不同的地方在于在iframe中请求到数据后如何带回来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建一个iframe
var iframe = document.createElement('iframe')
// 加载一个同域的页面 我们在这个页面发ajax
iframe.src = 'http://x.stuq.com:7001/public/post.html'
document.body.appendChild(iframe)
// 监听message事件
window.addEventListener('message', function(e) {
console.log(JSON.parse(e.data))
}, false);
// 进入post.html这个页面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// 第一个参数是传递的数据 第二个参数是那些页面可以接收到信息 * 表示所有 如果确切知道被谁使用 应该设置好具体的值
parent.postMessage(xhr.responseText, '*')
}
}
xhr.open('GET', 'http://x.stuq.com:7001/json', true)
xhr.send(null)

6. document.domain

当二级域名相同可以分别对 a.test.com/index.html 和 b.test.com/index.html 设置 document.domain = test.com
这样两个页面会当成同域处理

技术的适用场景

  • 最省力 设置Access-Control-Allow-Origin 为 * 但是不安全 不适用于比较私密的信息
  • 后端能控制 则使用JSONP
  • 后端无法控制 允许上传一个页面 window.name location.hash postMessage(前提是现代浏览器…)