不要再问我跨域的问题了,前端常见跨域解决方

作者: 前端知识  发布:2019-10-19

最后

瞩望看完这篇小说之后,再有人问跨域的主题素材,你能够嘴角微微上扬,冷笑一声:“不要再问小编跨域的难点了。”
扬长而去。

1 赞 6 收藏 评论

图片 1

五、 postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 第22中学的API,且是为数非常少能够跨域操作的window属性之一,它可用来化解以下地点的难题:
a.) 页面和其开采的新窗口的数据传递
b.) 多窗口之间音讯传递
c.) 页面与嵌套的iframe音讯传递
d.) 下面八个情景的跨域数据传递

用法:postMessage(data,origin)方法接受多个参数
data: html5职业帮助放肆基本项目或可复制的靶子,但有的浏览器只扶植字符串,所以传参时最佳用JSON.stringify()体系化。
origin: 公约 主机 端口号,也得以安装为”*”,表示能够传递给自由窗口,假若要钦定和日前窗口同源的话设置为”/”。

1.)a.html:(

<iframe id="iframe" src="" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); iframe.onload = function() { var data = { name: 'aym' }; // 向domain2传送跨域数据 iframe.contentWindow.postMessage(JSON.stringify(data), ''); }; // 接受domain2重回数据 window.add伊夫ntListener('message', function(e) { alert('data from domain2 ---> ' e.data); }, false); </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>      
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向domain2传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };
 
    // 接受domain2返回数据
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' e.data);
    }, false);
</script>

2.)b.html:(

<script> // 接收domain1的多寡 window.add伊芙ntListener('message', function(e) { alert('data from domain1 ---> ' e.data); var data = JSON.parse(e.data); if (data) { data.number = 16; // 管理后再发回domain1 window.parent.postMessage(JSON.stringify(data), ''); } }, false); </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
    // 接收domain1的数据
    window.addEventListener('message', function(e) {
        alert('data from domain1 ---> ' e.data);
 
        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;
 
            // 处理后再发回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

未曾同源战略限制的两大危殆境况

据自个儿打听,浏览器是从三个地点去做那么些同源计策的,一是对准接口的乞请,二是本着Dom的询问。试想一下未有那样的范围上述三种动作有哪些危险。

周围跨域场景

UEscortL 表达 是还是不是同意通讯 同一域名,区别文件或路线 允许 同一域名,区别端口 差异意 同一域名,分歧协商 不容许 域名和域名对应同样ip 不容许 主域同样,子域差异不容许 差异域名 分化意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
URL                                      说明                    是否允许通信
http://www.domain.com/a.js
http://www.domain.com/b.js         同一域名,不同文件或路径           允许
http://www.domain.com/lab/c.js
 
http://www.domain.com:8000/a.js
http://www.domain.com/b.js         同一域名,不同端口                不允许
http://www.domain.com/a.js
https://www.domain.com/b.js        同一域名,不同协议                不允许
http://www.domain.com/a.js
http://192.168.4.12/b.js           域名和域名对应相同ip              不允许
http://www.domain.com/a.js
http://x.domain.com/b.js           主域相同,子域不同                不允许
http://domain.com/c.js
http://www.domain1.com/a.js
http://www.domain2.com/b.js        不同域名                         不允许

毫无再问作者跨域的难点了

2018/07/16 · 基本功技艺 · 跨域

最早的作品出处: 写Bug   

写下那篇小说后本人想,要不未来就把这种基础的常见知识都归到那一个“不要再问笔者XX的主题素材”,变成一密密麻麻内容,希望大家看完之后再有人问您那一个难题,你心中会窃喜:“嘿嘿,是时候表现真正的技术了!”
一、毫不再问作者this的指向难题了

跨域那多少个字如同一块狗皮膏药同样黏在每叁个前端开垦者身上,无论你在做事上仍旧面试中无可制止会遇见那一个主题素材。为了敷衍面试,我每趟都不管背多少个方案,也不掌握怎么要这么干,反正面完就可以扔了,作者想做事上也不会用到那么多一塌糊涂的方案。到了确进行事,开辟蒙受有webpack-dev-server消除,上线了服务端的大佬们也会配好,配了怎样本身不管,反正不会跨域正是了。日子也就这么混过去了,终于有一天,小编以为不可能再持续那样混下去了,笔者决然要深透搞懂那一个东西!于是就有了那篇小说。

跨域施工方案

1、 通过jsonp跨域
2、 document.domain iframe跨域
3、 location.hash iframe
4、 window.name iframe跨域
5、 postMessage跨域
6、 跨域能源分享(CO君越S)
7、 nginx代理跨域
8、 nodejs中间件代理跨域
9、 WebSocket磋商跨域

跨域正确的张开药格局

由此对同源计谋的打听,我们相应要排除对浏览器的误会,同源战术是浏览器做的一件好事,是用来防备来自邪魔外道的口诛笔伐,但总不能够为了不让人渣进门而把全数人都拒之门外呢。没有错,大家这种正人君子只要展开药格局正确,就相应可以跨域。
上面将一个个演示准确张开药格局,但以前,有些希图专门的学问要做。为了当地演示跨域,大家要求:
1.随意跑起一份前端代码(以下前端是随意跑起来的vue),地址是:9099。
2.随便跑起一份后端代码(以下后端是随意跑起来的node koa2),地址是:9971。

后边叁个常见跨域应用方案(全)

2017/09/14 · 基本功技能 · 3 评论 · 跨域

原稿出处: 安静de沉淀   

同源战略限制下Dom查询的不错展开药格局

1.postMessage
window.postMessage() 是HTML5的多个接口,专一完成不相同窗口差异页面包车型地铁跨域通信。
为了演示方便,大家将hosts改一下:127.0.0.1 crossDomain.com,将来访谈域名crossDomain.com就非常访谈127.0.0.1。

这里是:9099/#/crossDomain,发新闻方

JavaScript

<template> <div> <button @click="postMessage">给; <iframe name="crossDomainIframe" src="; </div> </template> <script> export default { mounted () { window.addEventListener('message', (e) => { // 这里显明要对来源做校验 if (e.origin === '') { // 来自 console.log(e.data) } }) }, methods: { // 向 postMessage () { const iframe = window.frames['crossDomainIframe'] iframe.postMessage('我是[], 麻烦你查一下您那边有未有id为app的Dom', '') } } } </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
  <div>
    <button @click="postMessage">给http://crossDomain.com:9099发消息</button>
    <iframe name="crossDomainIframe" src="http://crossdomain.com:9099"></iframe>
  </div>
</template>
 
<script>
export default {
  mounted () {
    window.addEventListener('message', (e) => {
      // 这里一定要对来源做校验
      if (e.origin === 'http://crossdomain.com:9099') {
        // 来自http://crossdomain.com:9099的结果回复
        console.log(e.data)
      }
    })
  },
  methods: {
    // 向http://crossdomain.com:9099发消息
    postMessage () {
      const iframe = window.frames['crossDomainIframe']
      iframe.postMessage('我是[http://localhost:9099], 麻烦你查一下你那边有没有id为app的Dom', 'http://crossdomain.com:9099')
    }
  }
}
</script>

这里是:9099,接收音讯方

JavaScript

<template> <div> 我是 </div> </template> <script> export default { mounted () { window.addEventListener('message', (e) => { // 这里确定要对来源做校验 if (e.origin === '') { // console.log(e.data) // e.source能够是回信的靶子,其实正是 // e.origin能够用作targetOrigin e.source.postMessage(`我是[') ? '有id为app的Dom' : '没有id为app的Dom'}`, e.origin); } }) } } </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
  <div>
    我是http://crossdomain.com:9099
  </div>
</template>
 
<script>
export default {
  mounted () {
    window.addEventListener('message', (e) => {
      // 这里一定要对来源做校验
      if (e.origin === 'http://localhost:9099') {
        // http://localhost:9099发来的信息
        console.log(e.data)
        // e.source可以是回信的对象,其实就是http://localhost:9099窗口对象(window)的引用
        // e.origin可以作为targetOrigin
        e.source.postMessage(`我是[http://crossdomain.com:9099],我知道了兄弟,这就是你想知道的结果:${document.getElementById('app') ? '有id为app的Dom' : '没有id为app的Dom'}`, e.origin);
      }
    })
  }
}
</script>

结果能够看出:

图片 2

2.document.domain
这种方式只相符主域名同样,但子域名分歧的iframe跨域。
诸如主域名是:9099,子域名是:9099,这种意况下给五个页面钦赐一下document.domain即document.domain = crossdomain.com就能够访谈各自的window对象了。

3.canvas操作图片的跨域难点
本条相应是一个十分的冷门的跨域难点,张大神已经写过了自己就不再布鼓雷门了化解canvas图片getImageData,toDataUENCOREL跨域难点

四、 window.name iframe跨域

window.name属性的特殊之处:name值在区别的页面(以至差异域名)加载后还是存在,何况可以支撑特别长的 name 值(2MB)。

1.)a.html:(

var proxy = function(url, callback) { var state = 0; var iframe = document.createElement('iframe'); // 加载跨域页面 iframe.src = url; // onload事件会触发2次,第1次加载跨域页,并设有数据于window.name iframe.onload = function() { if (state === 1) { // 第2次onload(同域proxy页)成功后,读取同域window.name中多少 callback(iframe.contentWindow.name); destoryFrame(); } else if (state === 0) { // 第1次onload(跨域页)成功后,切换成同域代理页面 iframe.contentWindow.location = ''; state = 1; } }; document.body.appendChild(iframe); // 获取数据今后销毁这么些iframe,释放内部存款和储蓄器;那也确认保证了白山(不被其他域frame js访谈) function destoryFrame() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } }; // 诉求跨域b页面数据 proxy('', function(data){ alert(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
25
26
27
28
29
30
31
32
33
34
35
var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');
 
    // 加载跨域页面
    iframe.src = url;
 
    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
            callback(iframe.contentWindow.name);
            destoryFrame();
 
        } else if (state === 0) {
            // 第1次onload(跨域页)成功后,切换到同域代理页面
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1;
        }
    };
 
    document.body.appendChild(iframe);
 
    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};
 
// 请求跨域b页面数据
proxy('http://www.domain2.com/b.html', function(data){
    alert(data);
});

2.)proxy.html:(http://www.domain1.com/proxy….)
高级中学级代理页,与a.html同域,内容为空就可以。

3.)b.html:(

<script>     window.name = 'This is domain2 data!'; </script>

1
2
3
<script>
    window.name = 'This is domain2 data!';
</script>

总计:通过iframe的src属性由国外转向当地点,跨域数据即由iframe的window.name从国外传递到当地点。这些就高明地绕过了浏览器的跨域访谈限制,但相同的时间它又是平安操作。

同源计谋限制下接口央求的科学展开药形式

1.JSONP
在HTML标签里,一些标签例如script、img那样的猎取财富的标签是从未有过跨域限制的,利用那或多或少,大家得以这么干:

后端写个小接口

// 管理成功失利再次回到格式的工具 const {successBody} = require('../utli') class CrossDomain { static async jsonp (ctx) { // 前端传过来的参数 const query = ctx.request.query // 设置二个cookies ctx.cookies.set('tokenId', '1') // query.cb是内外端约定的点子名字,其实就是后端重返三个直接实施的主意给前端,由于前端是用script标签发起的央求,所以回来了那一个主意后也正是当下实践,并且把要回去的数额放在方法的参数里。 ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})` } } module.exports = CrossDomain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async jsonp (ctx) {
    // 前端传过来的参数
    const query = ctx.request.query
    // 设置一个cookies
    ctx.cookies.set('tokenId', '1')
    // query.cb是前后端约定的方法名字,其实就是后端返回一个直接执行的方法给前端,由于前端是用script标签发起的请求,所以返回了这个方法后相当于立马执行,并且把要返回的数据放在方法的参数里。
    ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
  }
}
module.exports = CrossDomain
 

简短版前端

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <script type='text/javascript'> // 后端再次回到直接推行的艺术,也正是推行那么些办法,由于后端把重临的数码放在方法的参数里,所以那边能获得res。 window.jsonpCb = function (res) { console.log(res) } </script> <script src='' type='text/javascript'></script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <script type='text/javascript'>
      // 后端返回直接执行的方法,相当于执行这个方法,由于后端把返回的数据放在方法的参数里,所以这里能拿到res。
      window.jsonpCb = function (res) {
        console.log(res)
      }
    </script>
    <script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>
  </body>
</html>

简轻巧单包装一下前端这一个套路

JavaScript

/** * JSONP哀告工具 * @param url 乞求的地址 * @param data 央求的参数 * @returns {Promise<any>} */ const request = ({url, data}) => { return new Promise((resolve, reject) => { // 管理传参成xx=yy&aa=bb的款式 const handleData = (data) => { const keys = Object.keys(data) const keysLen = keys.length return keys.reduce((pre, cur, index) => { const value = data[cur] const flag = index !== keysLen - 1 ? '&' : '' return `${pre}${cur}=${value}${flag}` }, '') } // 动态创设script标签 const script = document.createElement('script') // 接口重返的数据得到 window.jsonpCb = (res) => { document.body.removeChild(script) delete window.jsonpCb resolve(res) } script.src = `${url}?${handleData(data)}&cb=jsonpCb` document.body.appendChild(script) }) } // 使用方法 request({ url: '', data: { // 传参 msg: 'helloJsonp' } }).then(res => { console.log(res) })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* JSONP请求工具
* @param url 请求的地址
* @param data 请求的参数
* @returns {Promise<any>}
*/
const request = ({url, data}) => {
  return new Promise((resolve, reject) => {
    // 处理传参成xx=yy&aa=bb的形式
    const handleData = (data) => {
      const keys = Object.keys(data)
      const keysLen = keys.length
      return keys.reduce((pre, cur, index) => {
        const value = data[cur]
        const flag = index !== keysLen - 1 ? '&' : ''
        return `${pre}${cur}=${value}${flag}`
      }, '')
    }
    // 动态创建script标签
    const script = document.createElement('script')
    // 接口返回的数据获取
    window.jsonpCb = (res) => {
      document.body.removeChild(script)
      delete window.jsonpCb
      resolve(res)
    }
    script.src = `${url}?${handleData(data)}&cb=jsonpCb`
    document.body.appendChild(script)
  })
}
// 使用方式
request({
  url: 'http://localhost:9871/api/jsonp',
  data: {
    // 传参
    msg: 'helloJsonp'
  }
}).then(res => {
  console.log(res)
})

2.空iframe加form
精心的对象只怕发现,JSONP只好发GET央浼,因为精神上script加载财富就是GET,那么只要要发POST乞求怎么做呢?

后端写个小接口

// 处理成功战败再次来到格式的工具 const {successBody} = require('../utli') class CrossDomain { static async iframePost (ctx) { let postData = ctx.request.body console.log(postData) ctx.body = successBody({postData: postData}, 'success') } } module.exports = CrossDomain

1
2
3
4
5
6
7
8
9
10
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async iframePost (ctx) {
    let postData = ctx.request.body
    console.log(postData)
    ctx.body = successBody({postData: postData}, 'success')
  }
}
module.exports = CrossDomain

前端

const requestPost = ({url, data}) => { // 首先创设多少个用来发送数据的iframe. const iframe = document.createElement('iframe') iframe.name = 'iframePost' iframe.style.display = 'none' document.body.appendChild(iframe) const form = document.createElement('form') const node = document.createElement('input') // 注册iframe的load事件管理程序,借使您必要在响应再次来到时试行一些操作的话. iframe.add伊夫ntListener('load', function () { console.log('post success') }) form.action = url // 在钦赐的iframe中试行form form.target = iframe.name form.method = 'post' for (let name in data) { node.name = name node.value = data[name].toString() form.appendChild(node.cloneNode()) } // 表单成分须要增添到主文书档案中. form.style.display = 'none' document.body.appendChild(form) form.submit() // 表单提交后,就能够去除那一个表单,不影响下一次的数据发送. document.body.removeChild(form) } // 使用格局 requestPost({ url: '', data: { msg: 'helloIframePost' } })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const requestPost = ({url, data}) => {
  // 首先创建一个用来发送数据的iframe.
  const iframe = document.createElement('iframe')
  iframe.name = 'iframePost'
  iframe.style.display = 'none'
  document.body.appendChild(iframe)
  const form = document.createElement('form')
  const node = document.createElement('input')
  // 注册iframe的load事件处理程序,如果你需要在响应返回时执行一些操作的话.
  iframe.addEventListener('load', function () {
    console.log('post success')
  })
 
  form.action = url
  // 在指定的iframe中执行form
  form.target = iframe.name
  form.method = 'post'
  for (let name in data) {
    node.name = name
    node.value = data[name].toString()
    form.appendChild(node.cloneNode())
  }
  // 表单元素需要添加到主文档中.
  form.style.display = 'none'
  document.body.appendChild(form)
  form.submit()
 
  // 表单提交后,就可以删除这个表单,不影响下次的数据发送.
  document.body.removeChild(form)
}
// 使用方式
requestPost({
  url: 'http://localhost:9871/api/iframePost',
  data: {
    msg: 'helloIframePost'
  }
})

3.CORS

CO奥迪Q7S是贰个W3C标准,全称是”跨域能源共享”(Cross-origin resource sharing)跨域财富分享 CO福睿斯S 详解。看名字就理解那是拍卖跨域难点的正规做法。COWranglerS有二种央浼,简单乞求和非轻巧央求。

此间引用上边链接阮一峰先生的稿子证实一下简便央浼和非简单恳求。
浏览器将COTiguanS央求分成两类:轻巧诉求(simple request)和非简单诉求(not-so-simple request)。

万一起有时候满意以下两大原则,就属于轻巧诉求。
(1) 伏乞方法是以下二种办法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头音信不超越以下三种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于多个值application/x-www-form-urlencoded、multipart/form-data、text/plain

1.简易诉求
后端

// 管理成功失利重临格式的工具 const {successBody} = require('../utli') class CrossDomain { static async cors (ctx) { const query = ctx.request.query // *时cookie不会在http乞求中带上 ctx.set('Access-Control-Allow-Origin', '*') ctx.cookies.set('tokenId', '2') ctx.body = successBody({msg: query.msg}, 'success') } } module.exports = CrossDomain

1
2
3
4
5
6
7
8
9
10
11
12
13
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async cors (ctx) {
    const query = ctx.request.query
    // *时cookie不会在http请求中带上
    ctx.set('Access-Control-Allow-Origin', '*')
    ctx.cookies.set('tokenId', '2')
    ctx.body = successBody({msg: query.msg}, 'success')
  }
}
module.exports = CrossDomain
 

前端什么也不用干,就是符合规律发伏乞就足以,要是急需带cookie的话,前后端都要安装一下,上边这几个非轻松央浼例子拜谒到。

fetch(` => { console.log(res) })

1
2
3
fetch(`http://localhost:9871/api/cors?msg=helloCors`).then(res => {
  console.log(res)
})

2.非轻巧易行央求
非轻松须要会发出一回预检验乞请,再次来到码是204,预检查测验通过才会真的发出诉求,那才回到200。这里透过前端发诉求的时候增添一个特出的headers来触发非轻巧须要。
图片 3

后端

// 管理成功战败重临格式的工具 const {successBody} = require('../utli') class CrossDomain { static async cors (ctx) { const query = ctx.request.query // 若是急需http要求中带上cookie,要求前后端都安装credentials,且后端设置钦命的origin ctx.set('Access-Control-Allow-Origin', '') ctx.set('Access-Control-Allow-Credentials', true) // 非简要伏乞的COQashqaiS央浼,会在正儿八经通讯从前,扩张一回HTTP查询央求,称为"预检"央浼(preflight) // 这种状态下除了设置origin,还亟需设置Access-Control-Request-Method以至Access-Control-Request-Headers ctx.set('Access-Control-Request-Method', 'PUT,POST,GET,DELETE,OPTIONS') ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, t') ctx.cookies.set('tokenId', '2') ctx.body = successBody({msg: query.msg}, 'success') } } module.exports = CrossDomain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async cors (ctx) {
    const query = ctx.request.query
    // 如果需要http请求中带上cookie,需要前后端都设置credentials,且后端设置指定的origin
    ctx.set('Access-Control-Allow-Origin', 'http://localhost:9099')
    ctx.set('Access-Control-Allow-Credentials', true)
    // 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
    // 这种情况下除了设置origin,还需要设置Access-Control-Request-Method以及Access-Control-Request-Headers
    ctx.set('Access-Control-Request-Method', 'PUT,POST,GET,DELETE,OPTIONS')
    ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, t')
    ctx.cookies.set('tokenId', '2')
 
    ctx.body = successBody({msg: query.msg}, 'success')
  }
}
module.exports = CrossDomain

三个接口将要写这么多代码,倘诺想有所接口都合併管理,有怎么着更高尚的法子啊?见下面包车型地铁koa2-cors。

const path = require('path') const Koa = require('koa') const koaStatic = require('koa-static') const bodyParser = require('koa-bodyparser') const router = require('./router') const cors = require('koa2-cors') const app = new Koa() const port = 9871 app.use(bodyParser()) // 管理静态财富 这里是前端build好未来的目录 app.use(koaStatic( path.resolve(__dirname, '../dist') )) // 处理cors app.use(cors({ origin: function (ctx) { return '' }, credentials: true, allowMethods: ['GET', 'POST', 'DELETE'], allowHeaders: ['t', 'Content-Type'] })) // 路由 app.use(router.routes()).use(router.allowedMethods()) // 监听端口 app.listen(9871) console.log(`[demo] start-quick is starting at port ${port}`)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const path = require('path')
const Koa = require('koa')
const koaStatic = require('koa-static')
const bodyParser = require('koa-bodyparser')
const router = require('./router')
const cors = require('koa2-cors')
const app = new Koa()
const port = 9871
app.use(bodyParser())
// 处理静态资源 这里是前端build好之后的目录
app.use(koaStatic(
  path.resolve(__dirname, '../dist')
))
// 处理cors
app.use(cors({
  origin: function (ctx) {
    return 'http://localhost:9099'
  },
  credentials: true,
  allowMethods: ['GET', 'POST', 'DELETE'],
  allowHeaders: ['t', 'Content-Type']
}))
// 路由
app.use(router.routes()).use(router.allowedMethods())
// 监听端口
app.listen(9871)
console.log(`[demo] start-quick is starting at port ${port}`)
 

前端

fetch(`, { // 供给带上cookie credentials: 'include', // 这里增加额外的headers来触发非轻便央浼 headers: { 't': 'extra headers' } }).then(res => { console.log(res) })

1
2
3
4
5
6
7
8
9
10
fetch(`http://localhost:9871/api/cors?msg=helloCors`, {
  // 需要带上cookie
  credentials: 'include',
  // 这里添加额外的headers来触发非简单请求
  headers: {
    't': 'extra headers'
  }
}).then(res => {
  console.log(res)
})

4.代理
想转手,假使大家乞请的时候照旧用前端的域名,然后有个东西帮大家把这些央浼转载到真正的后端域名上,不就防止跨域了吗?那时候,Nginx出场了。
Nginx配置

server{ # 监听9099端口 listen 9099; # 域名是localhost server_name localhost; #凡是localhost:9099/api这么些样子的,都转载到确实的服务端地址 location ^~ /api { proxy_pass ; } }

1
2
3
4
5
6
7
8
9
10
server{
    # 监听9099端口
    listen 9099;
    # 域名是localhost
    server_name localhost;
    #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
    location ^~ /api {
        proxy_pass http://localhost:9871;
    }    
}

前面一个就绝不干什么事情了,除了写接口,也没后端什么事情了

// 须求的时候一向用回前端那边的域名 fetch('', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ msg: 'helloIframePost' }) })

1
2
3
4
5
6
7
8
9
10
11
// 请求的时候直接用回前端这边的域名http://localhost:9099,这就不会跨域,然后Nginx监听到凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
fetch('http://localhost:9099/api/iframePost', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    msg: 'helloIframePost'
  })
})

Nginx转载的办法就好像很平价!但这种利用也是看现象的,假使后端接口是贰个共用的API,比方有的公共服务获取天气什么的,前端调用的时候总无法让运转去布置一下Nginx,借使宽容性没难点(IE 10要么以上),CROS才是更通用的做法呢。

怎样是跨域?

跨域是指二个域下的文书档案或脚本企图去乞求另二个域下的能源,这里跨域是广义的。

广义的跨域:

1.) 能源跳转: A链接、重定向、表单提交 2.) 能源嵌入:<link>、<script>、<img>、<frame>等dom标签,还会有样式中background:url()、@font-face()等公事外链 3.) 脚本央浼: js发起的ajax央浼、dom和js对象的跨域操作等

1
2
3
1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入:<link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等

事实上大家经常所说的跨域是狭义的,是由浏览器同源计谋限制的一类须求场景。

什么是同源策略?
同源战略/SOP(萨姆e origin policy)是一种约定,由Netscape公司一九九八年引进浏览器,它是浏览器最基本也最核烟酸心得安全作用,要是缺乏了同源计谋,浏览器很轻易受到XSS、CSF揽胜极光等攻击。所谓同源是指”左券 域名 端口”三者一致,尽管几个分裂的域名指向同多个ip地址,也非同源。

同源计策限制之下两种行为:

1.) Cookie、LocalStorage 和 IndexDB 不或者读取 2.) DOM 和 Js对象不可能获得3.) AJAX 伏乞不可能发送

1
2
3
1.) Cookie、LocalStorage 和 IndexDB 无法读取
2.) DOM 和 Js对象无法获得
3.) AJAX 请求不能发送

未有同源计策限制的Dom查询

1.有一天你刚睡醒,收到一封邮件,说是你的银行账号有高风险,赶紧点进www.yinghang.com改密码。你吓尿了,赶紧点步向,照旧熟稔的银行登入分界面,你坚决输入你的账号密码,登陆进去看看钱有未有少了。
2.睡眼朦胧的你没看清楚,平常做客的银行网址是www.yinhang.com,而现行反革命作客的是www.yinghang.com,那些钓鱼网址做了什么样吗?

// HTML <iframe name="yinhang" src="www.yinhang.com"></iframe> // JS // 由于并未有同源战略的限制,钓鱼网址可以直接获得其他网址的Dom const iframe = window.frames['yinhang'] const node = iframe.document.getElementById('你输入账号密码的Input') console.log(`得到了这一个${node},笔者还拿不到您刚好输入的账号密码吗`)

1
2
3
4
5
6
7
// HTML
<iframe name="yinhang" src="www.yinhang.com"></iframe>
// JS
// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom
const iframe = window.frames['yinhang']
const node = iframe.document.getElementById('你输入账号密码的Input')
console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)

透过大家理解,同源计策确实能躲避一些险恶,不是说有了同源战略就安枕而卧,只是说同源计策是一种浏览器最核烟酸心得安全机制,究竟能拉长一点攻击的基金。其实并没有刺不穿的盾,只是攻击的本钱和口诛笔伐成功后获得的利润成不成正比。

2、 nginx反向代理接口跨域

跨域原理: 同源计策是浏览器的安全战略,不是HTTP左券的一有的。服务器端调用HTTP接口只是使用HTTP合同,不会实践JS脚本,无需同源战术,也就不设有赶上难题。

福如东海思路:通过nginx配置二个代理服务器(域名与domain1一样,端口分歧)做跳板机,反向代理访谈domain2接口,并且能够顺便修改cookie中domain新闻,方便当前域cookie写入,达成跨域登陆。

nginx具体配置:

#proxy服务器 server { listen 81; server_name www.domain1.com; location / { proxy_pass ; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口访谈nignx时,此时无浏览器参预,故未有同源限制,上边包车型地铁跨域配置可不启用 add_header Access-Control-Allow-Origin ; #眼下端只跨域不带cookie时,可为* add_header Access-Control-Allow-Credentials true; } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;
 
    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;
 
        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

1.) 前端代码示例:

var xhr = new XMLHttpRequest(); // 前端开关:浏览器是还是不是读写cookie xhr.withCredentials = true; // 访谈nginx中的代理服务器 xhr.open('get', '', true); xhr.send();

1
2
3
4
5
6
7
8
var xhr = new XMLHttpRequest();
 
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
 
// 访问nginx中的代理服务器
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();

2.) Nodejs后台示例:

var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) { var params = qs.parse(req.url.substring(2)); // 向前台写cookie res.writeHead(200, { 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本不恐怕读取 }); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
 
server.on('request', function(req, res) {
    var params = qs.parse(req.url.substring(2));
 
    // 向前台写cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:脚本无法读取
    });
 
    res.write(JSON.stringify(params));
    res.end();
});
 
server.listen('8080');
console.log('Server is running at port 8080...');

并没有同源计策限制的接口诉求

有五个一点都不大的事物叫cookie我们应该明了,经常用来管理登入等处境,指标是让服务端知道什么人发出的此次央浼。假如你诉求了接口实行登陆,服务端验证通过后会在响应头加入Set-Cookie字段,然后下一次再发央浼的时候,浏览器会自动将cookie附加在HTTP要求的头字段Cookie中,服务端就能够清楚那个客商已经报到过了。知道这几个以往,大家来看现象:
1.你筹算去清空你的购物车,于是展开了买买买网址www.maimaimai.com,然后登陆成功,一看,购物车东西如此少,不行,还得买多点。
2.您在看有何事物买的进度中,你的好亲密的朋友发给你贰个链接www.nidongde.com,一脸yin笑地跟你说:“你懂的”,你坚决展开了。
3.您饶有兴致地浏览着www.nidongde.com,什么人知那几个网址暗地里做了些不可描述的事务!由于未有同源计策的限定,它向www.maimaimai.com发起了央浼!聪明的你一定想到上边的话“服务端验证通过后会在响应头参与Set-Cookie字段,然后下一次再发诉求的时候,浏览器会自动将cookie附加在HTTP央浼的头字段Cookie中”,那样一来,那些不法兰西网球公开赛站就也正是登入了你的账号,可认为所欲为了!借使这不是贰个买买买账号,而是你的银行账号,这……
那正是典故中的CS福睿斯F攻击浅谈CS宝马X3F攻击格局。
看了那波CS兰德奥迪Q5F攻击作者在想,固然有了同源攻略限制,但cookie是明火执杖的,还不是同样能拿下来。于是自个儿看了有个别cookie相关的篇章聊一聊 cookie、Cookie/Session的编写制定与淮北,知道了服务端能够安装httpOnly,使得前端十分的小概操作cookie,若无如此的设置,像XSS攻击就足以去取获得cookieWeb安全测验之XSS;设置secure,则有限协助在https的加密通讯中传输防止截获。

一、 通过jsonp跨域

常常为了缓解web服务器的载重,大家把js、css,img等静态能源抽离到另一台独立域名的服务器上,在html页面中再经过相应的价签从分歧域名下加载静态财富,而被浏览器允许,基于此原理,大家得以由此动态创制script,再乞请三个带参网站完成跨域通讯。

1.)原生完成:

<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参并钦点回调施行函数为onBack script.src = ''; document.head.appendChild(script); // 回调实践函数 function onBack(res) { alert(JSON.stringify(res)); } </script>

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
    var script = document.createElement('script');
    script.type = 'text/javascript';
 
    // 传参并指定回调执行函数为onBack
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
    document.head.appendChild(script);
 
    // 回调执行函数
    function onBack(res) {
        alert(JSON.stringify(res));
    }
</script>

服务端再次来到如下(再次回到时即推行全局函数):

onBack({"status": true, "user": "admin"})

1
onBack({"status": true, "user": "admin"})

2.)jquery ajax:

$.ajax({ url: '', type: 'get', dataType: 'jsonp', // 央浼形式为jsonp jsonpCallback: "onBack", // 自定义回调函数名 data: {} });

1
2
3
4
5
6
7
$.ajax({
    url: 'http://www.domain2.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "onBack",    // 自定义回调函数名
    data: {}
});

3.)vue.js:

this.$http.jsonp('', { params: {}, jsonp: 'onBack' }).then((res) => { console.log(res); })

1
2
3
4
5
6
this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'onBack'
}).then((res) => {
    console.log(res);
})

后端node.js代码示例:

var querystring = require('querystring'); var http = require('http'); var server = http.createServer(); server.on('request', function(req, res) { var params = qs.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp再次回到设置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn '(' JSON.stringify(params) ')'); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
 
server.on('request', function(req, res) {
    var params = qs.parse(req.url.split('?')[1]);
    var fn = params.callback;
 
    // jsonp返回设置
    res.writeHead(200, { 'Content-Type': 'text/javascript' });
    res.write(fn '(' JSON.stringify(params) ')');
 
    res.end();
});
 
server.listen('8080');
console.log('Server is running at port 8080...');

jsonp劣势:只好兑现get一种哀告。

要明白跨域,首先要知道怎会有跨域那么些题目出现

当真,大家这种搬砖工人就是为着混口饭吃嘛,好好的调个接口告诉笔者跨域了,这种阻碍我们轻易搬砖的事体真恶心!为啥会跨域?是什么人在搞职业?为了找到那些主题素材的始作俑者,请点击浏览器的同源攻略。
那般官方的东西真难懂,没涉及,起码你理解了,因为浏览器的同源计策导致了跨域,正是浏览器在搞专业。
所以,浏览器为啥要搞工作?就是不想给好日子大家过?对于如此的质询,浏览器甩锅道:“同源计谋限制了从同二个源加载的文书档案或脚本怎么着与来自另五个源的能源开展互动。那是二个用以隔断潜在恶意文件的第一安全部制。”
诸如此比官方的话术真难懂,没提到,起码你知道了,就像那是个安全部制。
故而,究竟为啥要求这么的平安体制?那样的中卫机制消除了怎么难题?别急,让大家承继商量下去。

1、 前端设置:

1.)原生ajax

// 前端安装是不是带cookie xhr.withCredentials = true;

1
2
// 前端设置是否带cookie
xhr.withCredentials = true;

躬体力行代码:

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest宽容 // 前端安装是或不是带cookie xhr.withCredentials = true; xhr.open('post', '', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.send('user=admin'); xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); } };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
 
// 前端设置是否带cookie
xhr.withCredentials = true;
 
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
 
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }
};

2.)jQuery ajax

$.ajax({ ... xhrFields: { withCredentials: true // 前端安装是还是不是带cookie }, crossDomain: true, // 会让央求头中含有跨域的额外音讯,但不会含cookie ... });

1
2
3
4
5
6
7
8
$.ajax({
    ...
   xhrFields: {
       withCredentials: true    // 前端设置是否带cookie
   },
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
    ...
});

3.)vue框架
在vue-resource封装的ajax组件中步向以下代码:

Vue.http.options.credentials = true

1
Vue.http.options.credentials = true
1、 nginx配置消除iconfont跨域

浏览器跨域访问js、css、img等平常静态财富被同源计谋认同,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态财富服务器中步入以下配置。

location / { add_header Access-Control-Allow-Origin *; }

1
2
3
location / {
  add_header Access-Control-Allow-Origin *;
}

本文由金沙澳门官网发布于前端知识,转载请注明出处:不要再问我跨域的问题了,前端常见跨域解决方

关键词: 金沙澳门官网

上一篇:小说阅读
下一篇:没有了