搞懂跨域,生活从此告别996

164 阅读4分钟

什么是跨域

一般来说,当一个请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域。最常见的就是在一个域名下的网页中,调用另一个域名中的资源。
当浏览器报这样的错的时候,就是跨域请求出问题了

image.png

为什么要跨域

主要是为了安全,浏览器采用同源策略,对js进行限制,防止恶意用户获取非法数据,同时还防止了大部分XSS攻击(就是向用户界面注入js脚本)。
浏览器的两种同源策略会造成跨域问题:

  • DOM同源策略。禁止对不同源的页面的DOM进行操作,主要包括iframe、canvas之类的。不同源的iframe禁止数据交互的,含有不同源数据的canvas会受到污染而无法进行操作。
  • XmlHttpRequest同源策略。简单来说就禁止不同源的AJAX请求,主要用来防止CSRF攻击。

解决跨域

jsonp

jsonp跨域其实也是JavaScript设计模式中的一种代理模式。在html页面中通过相应的标签从不同域名下加载静态资源文件是被浏览器允许的,所以我们可以通过这个“犯罪漏洞”来进行跨域。一般,我们可以动态的创建script标签,再去请求一个带参网址来实现跨域通信

    //原生的实现方式 
    let script = document.createElement('script'); 
    
    script.src = 'http://www.nealyang.cn/login?username=Nealyang&callback=callback';
    
    document.body.appendChild(script);
    
    function callback(res) {
    
        console.log(res); 
        
    }

2. CORS

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

简单请求

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

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

  • GET
  • HEAD
  • POST

条件2 :Content-Type 的值仅限于下列三者之一 :

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;

复杂请求

不符合以上条件的请求就肯定是复杂请求了。复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请,称为"预检"请求,该请求是option方法的 , 通过该请求来知道服务端是否允许跨域请求。
我们用 PUT 向后台请求时, 属于复杂请求,后台需如下配置:

// 允许哪个方法访问我
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// 预检的存活时间
res.setHeader('Access-Control-Max-Age', 6)
// OPTIONS请求不做任何处理
if (req.method === 'OPTIONS') {
  res.end() 
}
// 定义后台返回的内容
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.end('是的我不爱你了')
})

接下来我们看下一个完整请求的例子,并且介绍下CORS请求相关的字段

//index.html
// 前端代码
<script>
    let xhr = new XMLHttpRequest();
    document.cookie = 'name=hw';
    xhr.withCredentials = true; //前端设置是否带 cookie
    xhr.open('PUT','http://localhost:4000/getData',true);
    xhr.setRequestHeader('name','hw');
    xhr.onreadystatechange = function() {
        if(xhr.readyState === 4) {
            if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                console.log(JSON.parse(xhr.response));
                console.log(xhr.getResponseHeader('name'))
            }
        }
    }
    xhr.send();
</script>
// server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname))
app.listen(3000)

// 后端代码
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] //设置白名单
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whitList.includes(origin)) {
    // 设置哪个源可以访问我
    res.setHeader('Access-Control-Allow-Origin', origin)
    // 允许携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // 允许哪个方法访问我
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true)
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age', 6)
    // 允许返回的头
    res.setHeader('Access-Control-Expose-Headers', 'name')
    if (req.method === 'OPTIONS') {
      res.end() // OPTIONS请求不做任何处理
    }
  }
  next()
})
app.put('/getData', function(req, res) {
  let data = {
      username : 'zs',
      password : 123456
  }
  console.log(req.headers)
  res.setHeader('name', 'jw') //返回一个响应头,后台需设置
  res.end(JSON.stringify(data))
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('he')
})
app.listen(4000)

本地代理(vue方案)

 在vue.config.js中添加如下代码

 devServer: {
    proxy: {
      // 设置代理服务器解决跨域问题
      '/api': {
        target: 'http://localhost:3000'
      }
    }
  },

 /api代表代理的路径名,target代表代理的地址

postMessage

这是由 H5 提出来的一个 API ,它的作用就是向外界窗口发送信息,我们可以采用这种方式来规避同源策略的限制。

它可以做哪些事情:

  1. 页面和其打开的新窗口的数据传递
  2. 多窗口之间消息传递
  3. 页面与嵌套的 iframe 消息传递
otherWindow.postMessage(message,targetOrigin);

otherWindow 指的是目标窗口,也就是要给哪一个 window 发送消息, Message 是要发送的消息,类型为 StringObject(IE8、9不支持Obj)targetOrigin 是限定消息接受范围,不限制就用星号 *