跨域的几种常见解决方案

4/3/2023 跨域

# 什么是跨域

跨域不是问题,是一种安全机制。浏览器有一种策略叫为同源策略,同源策略规定了部分请求不能被浏览器梭接受。如果缺少了同源策略,浏览器很容起收到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)
})
1
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);}
});
1
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'
1
2

这就等于告诉浏览器,http://192.168.0.102:8080 这个地址是安全的,请不要拦截。
这样,http://192.168.0.103:8080 就可以接受来自 http://192.168.0.102:8080 返回的信息。
当然,我们也可以进行所有域名均不拦截的设置(如下):

//响应头
// * 代表所有域名均不拦截
Access-Control-Allow-Origin':'*'
1
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; #真实服务器的地址
        }
}
1
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)
}
1
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才是真正的称得上跨域请求解决方案,因为请求存在跨域,结果是拿到了数据,也就是说服务器和浏览器之间进行了协商通信控制后,才得以允许或拒绝。

上次更新: 2024/09/27 10:53:32
最近更新
01
react教程
03-01
02
前端抓包神器whistle
09-01
03
JsonItemExporter 和 JsonLinesItemExporter 保存数据的异同
08-18