前端常见跨域方案汇总

为什么会存在同源策略

概念

1995年有Netscape公司引入的一个安全策略,现在所有浏览器都在使用这个策略。它限制了一个源从另外一个源请求资源,用于隔离潜在恶意文件。

什么才是不同的源

我们都知道一般的地址又下面三部分组成

  • 协议相同
  • 域名相同
  • 端口相同

只要两个地址其中有一个不相同,那么这两个地址就是不同的源。

举个例子

//地址
http://www.address.com/item/page.html

协议: http://
域名:www.example.com
端口:8080(http)/443(https) (端口默认省略)

http://www.address.com/item2/other.html:同源

http://address.com/item/other.html:不同源(域名不同)

http://v2.www.address.com/item/other.html:不同源(域名不同)

http://www.address.com:81/item/other.html:不同源(端口不同) 

同源的目的

目的是为了保护用户信息的安全,防止恶意网站窃取数据,否则Cookie可以共享。有的网站一般会把一些重要信息存放在cookie或者LocalStorage中,这时如果别的网站能够获取获取到这个数据,可想而知,这样就没有什么安全可言了。

限制范围

目前共有三种行为受到限制

  • Cookie、LocalStorage和IndexDB 无法读取
  • DOM无法获得
  • AJAX 请求不能发送

解决方案

1、通过jsonp跨域

原理

script、img、iframe等标签的src属性都拥有跨域请求资源的能力,我们可以把js、css、img等资源放到一个独立域名的服务器上。然后通过动态创建script标签,再请求一个回调函数的网址,服务器把需要传递的参数塞入这个回调函数中, 然后我们在js代码中执行这个函数就可已获取到你想要的参数。

前端原生实现

<script>
    window.xxx = function (value) {
         console.log(value)
    }

    var script = document.createElement('script')
    script.src = 'https://www.address.com:433/json?callback=xxx'
    document.body.appendChild(script)
</script>

jquery实现

   $.ajax({
             type: "get",
             url: "https://www.address.com:433/json",
             dataType: "jsonp",
             jsonp: "callback",
             jsonpCallback:"xxx"//自定义回调函数名
             success: function(res){
                console.log(res)
             },
             error: function(){
                 console.log('fail');
             }
         });

jsonp插件

//安装 
 npm install jsonp

const jsonp = require('jsonp');

jsonp('https://www.address.com:433/json', {parma:'xxx'}, (err, data) => {
  if (err) {
    console.error(err.message);
  } else {
    console.log(data);
  }
});

node.js egg 服务端

//需要看egg官网构建一个simple框架
egg-init --type=simple

// router.js  用egg内置jspnp方法  https://eggjs.org/api/Config.html#jsonp
module.exports = app => {
  app.get('/json', app.jsonp({ callback: 'xxx' }), app.controller.json.index)
}

缺点

  • 只支持GET请求,不支持POST请求
  • 调用失败没有HTTP状态码
  • 不够安全。
2、CORS

原理

CORS(Cross-Origin Resource Sharing)跨资源分享,浏览器不能低于IE10,服务器支持任何类型的请求。

熟悉几个后台需要设置的字段

  • Access-Control-Allow-Origin

字段必传,为*表示允许任意域名的请求。当有cookie需要传递时,需要指定域名。

  • Access-Control-Allow-Credentials

字段可选,默认为false,表示是否允许发送cookie。若允许,通知浏览器也要开启cookie值的传递。

  • Access-Control-Expose-Headers

字段可选。如果想要浏览器拿到getResponesHeader()其他字段,就在这里指定。

  • Access-Control-Request-Method

必须字段,非简单请求时设置的字段,例如PUT请求。

  • Access-Control-Request-Headers

指定额外的发送头信息,以逗号分割字符串。

前端原生代码

var xhr = new XMLHttpRequest()
// 设置携带cookie
xhr.withCredentials = true;
xhr.open('POST', 'https://www.address.com:433/json', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(null)

xhr.onreadystatechange = function () {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(JSON.parse(xhr.responseText).msg)
  }
}

后端代码

module.exports = app => {
  class CrosController extends app.Controller {
    * index(req) {
      this.ctx.set('Access-Control-Allow-Origin', 'https://www.address.com');
      this.ctx.set('Access-Control-Allow-Credentials', 'true')
      this.ctx.body = { msg: 'hello world' }
    }
  }
  return CrosController
}

优点

  • 支持所有类型的HTTP请求。

缺点

  • 不兼容IE10以下。
3、iframe结合locaction.hash方法

原理

也是利用iframe可以在不同域中传值的特点,而location.hash正好可以携带参数,所以利用iframe作为这个不同域之间的桥梁。

具体实现步骤

  • 1、向A域名页面插入一个B域名的iframe标签。
  • 2、然后在B域名的iframe页面中ajax请求同域名的服务器。
  • 3、iframe页面拿到数据后通过praent.location.href将想要传递的参数放到#后面,作为hash传递。
  • 4、这样在A域名页面就能通过window.onhashchange 监听处理你想要的数据。

A域名页面

var iframe = document.createElement('iframe')
iframe.src = 'http://www.B.com:80/hash.html'
document.body.appendChild(iframe)

window.onhashchange = function () {
  //处理hash
  console.log(location.hash)

}

B域名页面


var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        var res = JSON.parse(xhr.responseText)
        console.log(res.msg)
        parent.location.href = `http://www.A.com:80/a.html#msg=${res.msg}`
    }
}
xhr.open('GET', 'http://www.B.com:80/json', true)
xhr.send(null)

缺点

  • iframe虽然能解决问题,但是安全风险还是比较重要的。
  • hash传参处理起来比较麻烦。
4、iframe结合window.name方法

原理

原理其实是和上面的方法一样,区别在于window.name能够传递2MB以上的数据。

A域名页面

var iframe = document.createElement('iframe')
iframe.src = 'http://www.B.com:80/name.html'
document.body.appendChild(iframe)
var times = 0
iframe.onload = function () {
    if (times === 1) {
        console.log(JSON.parse(iframe.contentWindow.name))
        destoryFrame()
    } else if (times === 0) {
        times = 1
    }
}


// 获取数据以后销毁这个iframe,释放内存;
function destoryFrame() {
    document.body.removeChild(iframe);
}

B域名页面

var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        window.name = xhr.responseText
        location.href = 'http://www.A.com:80/a.html'
    }
}
xhr.open('GET', 'http://www.B.com:80/json', true)
xhr.send(null)
5、postMessage跨域

原理

postMessage是H5原生API支持,可以在两个页面或者多个页面,以及不同源页面之间传递数据。窗口之间能够传递数据的前提是必须从一个窗口获取到另外一个窗口的目标对象(target window),比如说用iframe打开另外一个窗口,我们得获取到这个iframe的contentWindow。或者通过window.open()打开另外一个窗口时会返回这个窗口的window对象。

参数传递

/**
  data  需要传递的数据,使用JSON.stringify 序列化
  origin 设置为'*'时,表示传递给所有窗口,也可以指定地址。 如果是同源下设置为'/'
**/
postMessage(data,origin)

http://localhost:8069 窗口

  // 打开一个新的窗口
  var popup = window.open('http://localhost:8080');

  /// 等待新窗口加载完
  setTimeout(function() {
      // 当前窗口向目标源传数据
    popup.postMessage({"age":10}, 'http://localhost:8080');
  }, 1000);

http://localhost:8080 窗口

  // 设置监听,如果有数据传过来,则打印
  window.addEventListener('message', function(e) {
    console.log(e);
//判断是不是目标地址
  if(e.origin !== 'http://localhost:8069')return;
    // console.log(e.source === window.opener);  // true
//发回数据
 e.source.postMessage({"age":20}, e.origin);
  });
其他的解决方式

以上五种是比较常见的跨域解决方案,各有优缺点。没有绝对的最优方案,只有最合适应用场景。

常见的两种代理跨域

  • nginx反向代理跨域 node中间件代理

配置就不讲了,其实我也不熟悉,平时工作用不到这么高大上的。就来讲讲原理以及思路。

什么是代理

既然是代理跨域,那么代理(Proxy Server)就是一个很重要的点,这里的代理说的服务器代理,是一种很重要的服务器安全功能,也是一种很常见的设计模式,来隔绝不同的模块,解耦模块。生活中也很容易见到这种模式,我们买房(买不起呀)买车,就好比跟一个大型服务器打交道,别人是大老板,自然不会那么容易亲自接待你,这时候有中介在中间,帮你们两方理清好思路,做好铺垫,然后后面的交流才会更加顺通高效。我们浏览器访问不同源的服务器是有限定的,但是nginx和node中间件这些代理就没有跨域的限定,所以我们可以放心的把任务交给他们,让他们帮我们去做我们做不了的事。

为什么代理是反的

我们知道单个服务器的处理能力是有限的,就好比如中国也不可能只有一家汽车生产商,中国那么大的市场,自然需要很多生产商才能满足的过来。那么用户选购的时候需要节省时间和精力,汽车之间就承担这样一个角色,把成千上万的用户需求分配出去,nginx就能够把用户的请求分发到空闲的服务器上,然后服务器返回自己的服务到负载均衡设备上,然后负载均衡的设备会讲服务器的服务返回给用户,所以我们并不知道为什么服务的是哪一台服务器发送出来的,这就很好的隐藏了服务器。有一句精辟的话是这么说的:“反向代理就是流量发散状,代理是流量汇聚状。”

最后附上一张画的很丑陋的图

反向代理.png

如果大神您想继续探讨或者学习更多知识,欢迎加入QQ或者微信一起探讨:854280588
QQ.png
微信
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 原文地址:原文地址 什么是跨域? 跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。 广义...
    C_Y大渔阅读 1,242评论 1 13
  • 什么是跨域? 2.) 资源嵌入:、、、等dom标签,还有样式中background:url()、@font-fac...
    电影里的梦i阅读 2,320评论 0 5
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    Yaoxue9阅读 1,224评论 0 6
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    他方l阅读 1,041评论 0 2
  • 我的父母一直认为我不会说话,掌握不好说话的艺术。然而我认为我是不愿意和长辈之间交谈,因为跟他们之间没什么好说...
    妖孽LWL阅读 446评论 0 0