一、同源策略
协议、域名、端口都相同称为同域,某个不相同就是跨域
为什么浏览器不支持跨域?
- 浏览器中的 cookie, localStorage 都仅支持同域
- DOM 元素也有同源策略 (iframe)
- ajax 也不支持跨域
二、实现跨域
2.1 、浏览器与服务器之间实现跨域
2.1.1、jsonp
介绍
- 浏览器动态创建
script
,src
指向服务器,同时传递一个查询参数cb=yyy
- 服务器接收请求后,根据查询参数
cb
, 构造如下的响应yyy.call(undefined, data)
- 浏览器接受响应后,会执行
yyy.call(undefined, data)
- 最后在函数
yyy
中就能拿到请求的数据
封装
js
function jsonp({url, params, cb}) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[cb] = function(data) {
resolve(data)
document.body.removeChild(script)
}
params = {...params, cb}
let query = Object.keys(params).reduce((pre, key) => {
pre.push(`${key}=${params[key]}`)
}, []).join('&')
script.src=`${url}?${query}`
document.body.appendChild(script)
})
}
// 使用
jsonp({
url: `www.baidu.com`,
params: {
wd: '123'
},
cb: 'show'
}).then(data => {
console.log(data)
})
缺点
- 因为
JSONP
是通过动态创建script
标签实现的,而script
只能发送GET
请求 - 不安全,存在
xss
攻击
2.1.2、cors
cors
主要是在服务端进行设置响应头,有如下设置
Access-Control-Allow-Origin
: 允许哪个源访问我,可设置*
Access-Control-Allow-Headers
: 允许携带哪个 Header 访问我,Access-Control-Allow-Methods
: 允许哪些方法访问我Access-Control-Max-Age
: 预检请求的有效时间Access-Control-Allow-Credentials
: 允许携带 CookieAccess-Control-Expose-Headers
: 允许前端获取哪个头
设置
Access-Control-Max-Age
,何为请求有效时间?
在发送 POST
,PUT
等请求时,通常需要发送请求体,而浏览器在真正发送请求之前,会发送一个预检请求,也就是 OPTIONS
请求,只有当预检请求通过后,浏览器才会发送真正的请求。
当设置 Access-Control-Max-Age = 6000
后,表示预检请求的结果在 6s 之内是有效的。
js
const express = require('express')
const app = express()
const whileList = [
'http://localhost:3000'
]
app.use(function(req, res, next){
const {origin} = req.headers
if (whileList.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin)
res.setHeader('Access-Control-Allow-Headers', 'name')
res.setHeader('Access-Control-Allow-Methods', 'post')
res.setHeader('Access-Control-Max-Age', 6000)
}
next()
})
app.get('/getData', (req, res) => {
console.log(req.headers)
res.end('Hello')
})
app.listen(4000)
2.2 页面之间实现跨域(iframe 场景)
以下所以示例都是基于 a, b 页面同域,通过 3000 端口访问,而 c 页面不同域,通过 4000 端口访问。而目的是 a 页面能拿到 c 页面中的数据
2.2.1、postMessage
(首推)
使用 postMessage
发送消息,使用 onMessage
监听接收消息
a.html
html
<!DOCTYPE html>
<body>
<h1>a.html</h1>
<iframe id="iframe" src="http://localhost:4000/c.html" onload="load()"></iframe>
<script>
function load() {
let iframe = document.getElementById('iframe')
iframe.contentWindow.postMessage('data form a.html', 'http://localhost:4000')
window.onmessage = function(e) {
console.log(`a.html接收到:`, e.data);
}
}
</script>
</body>
</html>
c.html
html
<!DOCTYPE html>
<body>
<h1>c.html</h1>
<script>
window.onmessage = function(e) {
console.log(`c.html接收到:`, e.data);
e.source.postMessage('data form c.html', e.origin)
}
</script>
</body>
</html>
2.2.2、window.name
实现原理:
c
将数据放到window.name
上a
在加载c
页面后将iframe
地址换成同源下的b
页面,此时会重新执行load
函数,而window.name
保存的数据并不会丢失
a.html
html
<!DOCTYPE html>
<body>
<h1>a.html</h1>
<iframe id="iframe" src="http://localhost:4000/c.html" onload="load()"></iframe>
<script>
let first = true
function load() {
let iframe = document.getElementById('iframe')
if (first) {
iframe.src="http://localhost:4000/b.html"
first = false
} else {
console.log(iframe.contentWindow.name)
}
}
</script>
</body>
</html>
c.html
html
<!DOCTYPE html>
<body>
<h1>c.html</h1>
<script>
window.name="hello world!"
</script>
</body>
</html>
2.2.3、location.hash
实现原理:
- a 将传递给 c 的数据放到 iframe src 的 hash 中
- c 将要传递的数据通过 hash 传给 b 页面
- 因为 a,b 同源,所以 a 能拿到 c 传给 b 的数据
html
<!DOCTYPE html>
<body>
<h1>a.html</h1>
<iframe id="iframe" src="http://localhost:4000/c.html#dataFromA"></iframe>
<script>
window.onhashchange = function(){
console.log(location.hash)
}
</script>
</body>
</html>
html
<!DOCTYPE html>
<body>
<h1>c.html</h1>
<script>
console.log(window.hash) // dataFromA
let iframe = document.createElement('iframe')
iframe.src = `http://localhost:3000/b.html#dataFromC`
document.appendChild(iframe)
</script>
</body>
</html>
html
<!DOCTYPE html>
<body>
<h1>b.html</h1>
<script>
console.log(window.hash) // dataFromC
window.parent.parent.location.hash = window.hash
</script>
</body>
</html>
2.2.6、document.domain
主要用在一级域名和二级域名之间通信
2.3、无跨域现在
2.3.1、websocket
属于高级 api, 常使用 socket.io
客户端
html<!DOCTYPE html> <body> <h1>a.html</h1> <script> const socket = new WebSocket('ws://localhost:4000') socket.onopen = function() { socket.send('data from client') } socket.onmessage = function(e) { console.log(`client received:`, e.data) } </script> </body> </html>
服务端
jslet express = require('express') let WebSocket = require('ws') const wss = new WebSocket.server({port: 4000}) wss.on('connection', ws => { ws.on('message', function(data) { console.log('data') ws.send('data from server') }) })