Day10 解决跨域的常见方法大全(整理)

198 阅读5分钟

每日一句

We get to decide what our story is.

释义:我们的故事由我们自己决定。

跨域是个啥,为什么会有跨域?

前言:在前端和后端交互过程中,经常会遇到跨域的情况,那这个跨域是什么意思?

概念理解

那我们来解释下跨域:

这是受到浏览器同源策略的影响,如果没有同源策略,浏览器很容易遭受一些攻击像xss, csrf等等。

同源策略是指协议+域名+端口都一致的情况叫同源,其他任意一种情况不同都是非同源或不同域。不同域之间访问资源就叫"跨域"。

同源策略限制了哪些内容?

  • cookie,localStorage, indexDB
  • dom
  • ajax

有些标签元素不受同源影响可以访问资源:img,script,link

很多初级开发看到了报跨域错误时,会认为请求没有发出去,这是错误的理解。

其实请求是发出去,后端服务器中可以看到日志正常返回了数据,只不过是被浏览器认为这是不安全的访问给拦截了而矣!

跨域的几种情况

由上面的概念不难看出,跨域的几种情况:

  • 协议不同 https://demo.com 与 http://demo.com
  • 域名不同 https://demo.com 与 http://www.demo.com
  • 端口不同 https://demo.com 与 https://demo.com:81
  • ip与域名不同 https://11.22.11.22 与 https://demo.com

解决跨域方法

1.script方式jsonp

因为script不受同源影响,可利用其封装一个jsonp方法

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 } 
  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:888/api', params: { toEnd: '利用jsonp获取数据' }, callback: 'say' }).then(data => { 
    console.log(data) 
})

服务器端简易代码:

let express = require('express') 
let app = express() 
app.get('/say', function(req, res) { 
    let { toEnd, callback } = req.query 
    res.end(`${callback}('利用jsonp获取数据')`) 
}) 
app.listen(3000)

利用jquery的jsonp

$.ajax({ 
    url:"http://demo.com/apiData", 
    dataType:"jsonp", 
    type:"get",//可省略 
    jsonpCallback:"say",//可省略 
    jsonp:"callback",//可省略 
    success:function (data){ 
        console.log(data);
    } 
 });

其他 $.getJSON(数据量小), $.post

2. iframe

  • document.domain

原理:参与传递数据的页面设置相同的域

如页面1: a.demo.com/a.html, 页面2: b.demo.com/b.html, 则把页面1和页面2都设置document.domain="demo.com".

  • document.location

原理: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

  • document.name

window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。

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

通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

3. cors

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)跨域资源共享 CORS 详解。这是跨域问题的标准做法。

CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现

image.png

CORS有两种请求,简单请求和非简单请求。

简单请求:get,post,head 之一, 且请求头是

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

请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

复杂请求:会在正式请求之前,增加HTTP查询请求,称为"预检"请求(preflight),该请求是 option 方法,该请求来判断服务端是否允许跨域。

后台需要设置常用属性(需要根据实际情况按需设置)

  • 带cookie 需要前后端都设置credentials,且后端设置指定的origin
ctx.set('Access-Control-Allow-Origin', 'http://localhost:8088')
ctx.set('Access-Control-Allow-Credentials', true)
  • 设置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')

4.反向代理

1.nginx中

location ^~ /api {
  proxy_pass http://localhost:8088;
}

2.vue.config.js配置

devserver {
 proxy: {
      '/api': {
        target: 'http://10.10.137.255:8081',
        changeOrigin: true,
        ws: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  }

5.websocket

[WebSocket] 规范定义了一种 API,可在网络浏览器和服务器之间建立“套接字”连接。简单地说:客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。

前端代码

<script>
let socket = new WebSocket('ws://localhost:3000');
socket.onopen = function() {
  scolet.send('前端发送的数据111')
}
socket.onmessage = function(data) {
  console.log(data)
}
</script>

后端代码

let app = require('express')()
let WebSocket = require('ws')
let wss = new WebSocket.Server({port:3000});
wss.on('connection', function(ws) {
    ws.on('message', function(data) {
      console.log(data)
      ws.send('后端发送的数据222')
    })
})


6.Node中间件

实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。

部分代码:

const express = require('express'); 
const { createProxyMiddleware } = require('http-proxy-middleware'); 
const app = express();

// 拦截http://localhost:5000/admin/的请求,转到目标服务器:http://localhost:8081/admin/
app.use('/admin/*', createProxyMiddleware({ target: 'http://localhost:8081', changeOrigin: true }));

//配置服务端口 
app.listen(5000, () => { console.log(`localhost:5000`); });

7.postMessage

1.使用场景

  • 与iframe里的页面传递数据
  • 多窗口之间数据传递
  • 与打开的新窗口传递数据

2.用法

otherWindow.postMessage(message, targetOrigin, [transfer]);

3.示例

// index.html
<iframe src="http://localhost:8080/a.html" frameborder="0" id="ifr" onload="load()"></iframe>
<script>
function load() {
    ifr.contentWindow.postmessage('我是index', 'http://localhost:8080')
    window.onmessage = function(e) { console.log(e.data) }
}
</script>
// a.html
<script>
window.onmessage = e => {
   e.source.postMessage(e.data, e.origin);
}
</script>

8.canvas操作图片的跨域问题

张大神解决了canvas图片getImageData,toDataURL跨域问题

总结

cors和反向代理是日常开发用的比较多的方案,如果你还在兼容老的浏览器可以用jsonp方式

浏览器开启跨域方式,此方法可以从源头关闭浏览器的同源策略(此方法不建议)

  • window
找到你安装的目录 
.\Google\Chrome\Application\chrome.exe --disable-web-security --user-data-dir=xxxx
  • Mac
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --disable-web-security --user-data-dir=~/Downloads/chrome-data

~/Downloads/chrome-data 这个目录可以自定义.

当然在开发中使用ajax跨域时,还可以利用chrome插件Allow CORS: Access-Control-Allow-Origin来实现跨域,用起来也是屡试不爽。