跨域总结

同源策略

同源定义:协议+域名+端口号。同源表示三者均相等。

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询。

无同源策略的危害

  1. 接口请求:从其他网站发起请求从而导致的CSRF攻击。
  2. DOM查询:由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom。从而更能轻易获取用户密码等隐私数据。

同源策略可以预防某些恶意行为,但实现合理的跨域请求对开发某些浏览器应用程序也是至关重要的。

跨域请求

JSONP

JSONP是JSON with padding(填充式 JSON 或参数式 JSON)的简写,JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. 客户端部分
// JSONP跨域
function checkJsonp() {
const script = document.createElement('script');
script.setAttribute('src', 'http://127.0.0.1:8081/jsonp?name=zxlg&age=26&callback=handleRes')
document.body.appendChild(script);
}
// 客户端响应处理
function handleRes(res) {
const { name, age } = res;
alert(name + ' is ' + age + ' years old');
}

// 2.服务端部分(express)
app.get('/jsonp', (req, res) => {
const { query } = req
const { name, age, callback } = query
const response = {
name,
age
}
res.end(callback + '(' + JSON.stringify(response) + ')')
})

其实JSONP也是需要服务端支持,服务端必须返回执行的代码,即Fn(param),所以一般后端使用cors机制处理跨域机制比较好。

空iframe+from

jsonp只能发送get请求,使用iframe+from可以发起post请求

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
30
31
32
33
34
35
36
37
38
39
40
const iframePost = ({url, data}) => {

// 首先创建一个用来发送数据的iframe.
const iframe = document.createElement('iframe')
iframe.name = 'iframePost'
iframe.style.display = 'none'
document.body.appendChild(iframe)
const form = document.createElement('form')
const node = document.createElement('input')
// 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
iframe.addEventListener('load', function () {
console.log('iframe post success');
})

form.action = url
// 在指定的iframe中执行form
form.target = iframe.name
form.method = 'post'
for (let name in data) {
node.name = name
node.value = data[name].toString()
form.appendChild(node.cloneNode())
}
// 表单元素需要添加到主文档中.
form.style.display = 'none'
document.body.appendChild(form)
form.submit()

// 表单提交后,就可以删除这个表单,不影响下次的数据发送.
document.body.removeChild(form)
}
// 使用方式
const checkIframe = () => {
iframePost({
url: 'http://127.0.0.1:8081/iframePost',
data: {
msg: 'iframe + from'
}
})
}

缺点是获取请求返回的数据会遇到跨域DOM查询的问题

CORS

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

简单请求与复杂请求

简单请求
  1. 请求方法是以下三种方法之一:HEAD, GET POST
  2. HTTP的头信息不超出以下几种字段:Accept, Accept-Language, Content-Language, Last-Event-ID, Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
服务器收到”预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

实现一个CORS跨域请求

客户端发起ajax请求

默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等)。通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。如果服务器接受带凭据的请求,会用下面的HTTP头部来响应。Access-Control-Allow-Credentials:true
如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

客户端使用axios发起请求:

1
2
3
4
5
6
7
8
9
axios({
baseURL: 'http://127.0.0.1:8081',
url: '/cors',
method: 'post',
withCredentials: false, // 要发送Cookie,服务端必须指定明确的、与请求网页一致的域名
data: {
name: 'zxlg'
}
})
服务器配置CORS

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。

1
2
3
4
5
//服务器端设置响应头,各个服务器实现方法略有不同
'Access-Control-Allow-Origin': 'http://127.0.0.1:8080' // 允许访问的源
'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE' // 允许使用的请求方法
'Access-Control-Allow-Headers':'Content-Type' // 允许的头部
'Access-Control-Allow-Credentials':'true'/'flase'

以express为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// allow custom header and CORS
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'Content-Type')
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')

if (req.method === 'OPTIONS') {
res.sendStatus(200) // 让options请求快速返回
} else {
next()
}
})

app.post('/cors', function (req, res) {
const { name } = req.body
res.json({
resMsg: `hello ${name}!`
})
})

跨域DOM查询

postMessage

1
2
3
4
5
window.postMessage() // 是HTML5的一个接口,专注实现不同窗口不同页面的跨域通讯。

window.addEventListener('message', (e)=> {
// 接收请求
})

参考文献