跳到主要内容

了解前端跨域解决方案

· 阅读需 6 分钟
鲸落

什么是跨域

什么是同源策略及其限制内容

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制了一下行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 和 JS 对象无法获取
  • Ajax请求发送不出去

跨域

当协议、域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。

注意:跨域请求产生时,请求是发出去了,也是有响应的,仅仅是浏览器同源策略,认为不安全,拦截了结果,不将数据传递我们使用罢了

URL说明是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同文件夹允许
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口不允许
http://www.a.com/a.js
https://www.a.com/b.js
域名和域名对应ip不允许
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同不允许
(cookie这种情况下也不允许访问)
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二级域名不允许
(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不同域名不允许

解决方案如下

cors

概念

  • CORS 需要浏览器和后端同时支持
  • 浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
  • 服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
  • 虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求复杂请求

简单请求

只要同时满足以下两大条件,就属于简单请求

  • 条件1:使用下列方法之一:

    • GET

    • HEAD

      • POST
  • 条件2:HTTP的头信息不超出以下几种字段

    • Accept

    • Accept-Language

    • Content-Language

    • Last-Event-ID

    • Content-Type 的值仅限于下列三者之一:

      • text/plain

      • multipart/form-data

      • application/x-www-form-urlencoded

复杂请求

不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。

JSONP

JSONP原理

利用 <script> 标签没有跨域限制,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。

JSONP和AJAX对比

JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

JSONP缺点

JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。

nginx反向代理

服务器配置代理服务器

#如果监听到请求接口地址是 www.xxx.com/api/page ,nginx就向http://www.yyy.com:9999/api/page这个地址发送请求
server {
listen 80;
server_name www.xxx.com;
#判过滤出含有api的请求
location /api/ {
proxy_pass http://www.yyy.com:9999; #真实服务器的地址
}
}

proxy代理服务器

// vue.config.js/webpack.config.js 
// 优点:可以配置多个代理,且可灵活控制请求是否走代理
// 缺点:配置繁琐,发起代理请求时必须加上配置好的前缀
module.exports={
devServer:{
proxy:{
'/api01':{
target:'http://xxx.xxx.xxx:5000',
changeOrigin:true,
// 重写请求,根据接口详情,判断是否需要
pathRewrite:{
'^/api01':''
}
},
'/api02':{
target:'http://xxx.xxx.xxx:5001',
changeOrigin:true,
// 重写请求,根据接口详情,判断是否需要
pathRewrite:{
'^/api02':''
}
}
}
}
}
// changeOrigin设置为true时,服务器收到的请求头的host与服务器地址相同
// changeOrigin设置为false时,服务器收到的请求头的host与前端地址相同

websocket

WebSocket 协议是一种基于 TCP 的通信协议,与 HTTP 协议不同,它不受同源策略的限制,没有使用HTTP响应头,因此也没有跨域的限制

window.name + iframe

window.name属性可设置或者返回存放窗口名称的一个字符串。他的神器之处在于name值在不同页面或者不同域下加载后依旧存在,没有修改就不会发生变化,并且可以存储非常长的name(2MB)

其中a.html和b.html是同域的,都是http://localhost:3000;而c.html是http://localhost:4000

 // a.html(http://localhost:3000/b.html)
<iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
<script>
let first = true
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
function load() {
if(first){
// 第1次onload(跨域页)成功后,切换到同域代理页面
let iframe = document.getElementById('iframe');
iframe.src = 'http://localhost:3000/b.html';
first = false;
}else{
// 第2次onload(同域b.html页)成功后,读取同域window.name中数据
console.log(iframe.contentWindow.name);
}
}
</script>

b.html为中间代理页,与a.html同域,内容为空。

 // c.html(http://localhost:4000/c.html)
<script>
window.name = '我不爱你'
</script>

location.hash + iframe

原理就是通过 url 带 hash ,通过一个非跨域的中间页面来传递数据

一开始 a.html 给 c.html 传一个 hash 值,然后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。 同样的,a.html 和 b.htm l 是同域的,都是 http://localhost:8000,而 c.html 是http://localhost:8080

// a.html
<iframe src="http://localhost:8080/hash/c.html#name1"></iframe>
<script>
  console.log(location.hash);
  window.onhashchange = function() {
    console.log(location.hash);
  };
</script>
// b.html
<script>
  window.parent.parent.location.hash = location.hash;
</script>
// c.html
<body></body>
<script>
  console.log(location.hash);
  const iframe = document.createElement("iframe");
  iframe.src = "http://localhost:8000/hash/b.html#name2";
  document.body.appendChild(iframe);
</script>

document.domain + iframe

该方式只能用于二级域名相同的情况下,比如 a.test.comb.test.com 适用于该方式。 只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。

实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

我们看个例子:页面a.zf1.cn:3000/a.html获取页面b.zf1.cn:3000/b.html中a的值

// a.html
<body>
helloa
<iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
<script>
document.domain = 'zf1.cn'
function load() {
console.log(frame.contentWindow.a);
}
</script>
</body>
// b.html
<body>
hellob
<script>
document.domain = 'zf1.cn'
var a = 100;
</script>
</body>

不允许iframe的设置

添加X-Frame-Options响应头,值为deny

Loading Comments...