# 什么是跨域
跨域不是问题,是一种安全机制。浏览器有一种策略叫为同源策略,同源策略规定了部分请求不能被浏览器梭接受。如果缺少了同源策略,浏览器很容起收到XSS、CSRF等攻击。
注:跨域是浏览器单方面的拒绝响应数据,服务器端是处理完毕并且做出了响应的。
# 什么是同源策略
所谓的同源是指“协议+域名+端口”三者相同,即便两个不同的域名指向同一个ip地址,也不是同源。
同源策略规定,自由选发送请求的那边和接受请求的那一边处于同源的情况下,浏览器才会接受响应。
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。
同源策略限制的内容:
- Cookie、LocalStorage、IndexedDB等存储性内容
- DOM节点
- AJAX请求发送后,结果被浏览器拦截
但是有三个标签是允许跨域加载资源的:
<img src=xxx />
<link href=xxx />
<script src=xxx />
# 常见跨域场景
发送地址 | 接受地址 | 说明 | 是否允许通信 |
---|---|---|---|
http://www.a.com/a.js | http://www.a.com/b.js | 同一域名 | 是 |
http://www.a.com/a/b.js | http://www.a.com/b/a.js | 同一域名下不同文件夹 | 是 |
http://www.a.com:8000/a.js | http://www.a.com/b.js | 同一域名,不同端口 | 否 |
https://www.a.com/a.js | http://www.a.com/b.js | 同一域名,不同协议 | 否 |
https://www.a.com/a.js | http://192.168.0.6/b.js | 域名和域名对应ip | 否 |
https://www.a.com/a.js | http://test.a.com/b.js | 主域相同,子域不同 | 否 |
https://www.a.com/a.js | http://a.com/b.js | 主域相同,子域不同(同上) | 否 |
https://www.a.com/a.js | http://www.b.com/b.js | 不同域名 | 否 |
注:
- 如果是协议和端口造成的跨域问题,前端是无能为力的
- 跨域的判断,不会根据域名对应的IP是否相同来判断,必须“协议、域名、端口”匹配。
- 请求跨域并不是请求发不出去,而是请求可以发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
# 常见跨域解决方案
# 1. jsonp
# 1)原理:
利用<script>
标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。在远古的web中,这是一种方案。但现在已经不用了。
# 2)jsonp 和 ajax
JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)
# 3)jsonp优缺点
JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
// index.html
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = function(data) {
resolve(data)
document.body.removeChild(script)
}
params = { ...params, callback } // wd=b&callback=show
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: { wd: 'Iloveyou' },
callback: 'show'
}).then(data => {
console.log(data)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4)jQuery的jsonp形式
jsonp和get都是异步请求的,不存在其他的请求方式和同步请求,且jquery默认就会给jsonp的请求清楚缓存。
$.ajax({
url:"http://crossdomain.com/jsonServerResponse",
dataType:"jsonp",
type:"get",
jsonpCallback:"show", //回调函数
success:function (data){
console.log(data);}
});
2
3
4
5
6
7
8
# 2. CORS
CORS需要浏览器和后端同时支持。IE8 和 9 需要通过XDomainRequest 来实现。
浏览器会自动进行CORS通信,实现CORS通信的关键是后端。只要后端实现了CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin
就可以开启CORS。该属性表示哪些域名可以访问资源,如果设置通配符,则表示所有网站都可以访问资源。
Access-Control-Allow-Origin
响应头的意思是,安全同行的请求。
示例:
http://192.168.0.103:8080 向http://192.168.0.102:8080 发送了请求,结果因为域名不一样,在返回信息的时候因为IP地址不一致被拦截。
但是如果http://192.168.0.102:8080 在响应头中的 Access-Control-Allow-Origin
字段中携带上属性值'http://192.168.0.103:8080' 如下
//响应头
Access-Control-Allow-Origin':'http:/ /192.168.0.103:8080'
2
这就等于告诉浏览器,http://192.168.0.102:8080 这个地址是安全的,请不要拦截。
这样,http://192.168.0.103:8080 就可以接受来自 http://192.168.0.102:8080 返回的信息。
当然,我们也可以进行所有域名均不拦截的设置(如下):
//响应头
// * 代表所有域名均不拦截
Access-Control-Allow-Origin':'*'
2
3
# 3. nginx反向代理
使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。nginx代理一般使用在生产环境。是服务端解决跨域的一种方案。
server {
listen 80;
server_name www.aa.com;
#判过滤出含有api的请求
location /api/ {
proxy_pass http://www.bb.com:8080; #真实服务器的地址
}
}
2
3
4
5
6
7
8
# 4. postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
postMessage()方法允许来自不同源的脚本采用异步的方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
// a.html
<iframe src="http://localhost:8080/b.html" frameborder="0" id="frame" onload="load()"></iframe>
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我爱你', 'http://localhost:8080') //发送数据
window.onmessage = function(e) { //接受返回数据
console.log(e.data) //我不爱你
}
}
</script>
// b.html
window.onmessage = function(e) {
console.log(e.data) //我爱你
e.source.postMessage('我不爱你', e.origin)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 其他
- websocket
- vue.config.js的本地proxy代理
- node中间件代理
# 总结
CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案。
JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。
日常工作中,用得比较多的跨域方案是cors和nginx反向代理
# 点评
跨域的解决方案思路两种,躲避绕过去和cors;
各种iframe方式可传递数据,但组织和控制代码逻辑太复杂,鸡肋。
jsonp前几年使用,现在浏览器兼容性高了,以及受限于仅get方式,逐步淘汰了。
nginx反向代理是绕过去的方式,是从古至今通吃的没完解决方案,缺点也许是服务器压力大一点,实际中那点压力根本不是大问题;同时反向代理更适合内部应用间访问和共享。
cors才是真正的称得上跨域请求解决方案,因为请求存在跨域,结果是拿到了数据,也就是说服务器和浏览器之间进行了协商通信控制后,才得以允许或拒绝。
← 前端模块化详解 prettier常用配置项 →