[面试复盘]前端如何解决跨域问题

312 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

前言

前几天原本是计划着10.17理解一下跨域问题的,毕竟有问题不能拖,奈何,最近属实是有点慌乱,那就今天上课摸鱼的时候了解一下吧

这边为了简单演示,我直接使用了Http-server,他其实就可以看作是一个服务器,只不过,里面的文件都是静态的,且全是你电脑上的文件

npm install --global http-server

然后你就可以cd到你想要的目录,在这个目录下,进行http-server

这样你就可以假装在一个服务器上获取到你这个目录里面的东西了

http-server.png

什么是跨域

简单来说,去发送一个请求,但是你请求不到任何内容,并且报错为NO 'access-control-allow-origin',那么这是你就需要考虑,你的请求是不是跨域了

那么什么是跨域?

这是因为,出于安全性的考虑,浏览器具有同源策略

为什么需要同源策略

现在我们来假设一种情况

你打开某银行,但是这时候。不小心打开了一个恶意站点,在没有任何安全措施的情况下,那么那个恶意站点可能就可以获取到你的用户名,密码。或者注入(在这个银行站点插入js脚本)

所以说,为了避免这些,我们出现了这个同源策略

什么是同源策略

同源:

简单来说,就是保证你的protocol,port,host三者相同,那么就是同源的

同源策略:

  • 限制从一个源加载的文档或者脚本如何与来自另一个源的资源进行交互
  • 他是一个浏览器的行为,意思就是,其实你是发送了请求到服务器的,服务器也会返回数据,但是,浏览器的同源策略,将来自不同域的数据给拦截了

非同源未解决情况.png

我们可以看到,其实他的请求是响应了的,只是浏览器拦截了请求回来的东西

细分同源策略

  • DOM同源策略:禁止不同域页面的DOM进行操作
  • XMLHttpRequest同源策略:向不同的域的服务器发送请求

DOM同源策略:

现在假如有两个页面,他们是同源的,那么第二个页面完全可以操作第一个页面的DOM元素

let pdom = window.opener.document;//opener指向第一个页面的window对象
pdom.body.style.display = 'none'

但是如果两个不是同源的,那么就是不可以操作

XMLHttpRequest同源策略:

http://store.company.com/dir/other.htmlhttp://company.com/dir/page.html发送请求,那么会因为domain不相同,不可以请求

跨域网络访问

  1. <script src=...></script>,标签嵌入跨域脚本,语法错误信息只能被同源脚本捕捉到
  2. <link rel='stylesheet' href=''></link>,标签嵌入CSS,css的松散语法规则,CSS的跨域需要设置一个正确的HTTPContent-type
  3. <img><video>,<audio>
  4. 字体@font-face

简单来说,就是这些就是你就算是非同源,你也不需要进行配置,直接就可以获取到的

解决措施

  • 一般在开发环境中,无论是vue还是react,我们可以配置webpack

    参考链接:DevServer | webpack 中文文档 (docschina.org)

     proxy: {
          '/api': {
            target: 'http://localhost:3000',
            pathRewrite: { '^/api': '' },
            changeOrigin: true,
          },
       }
    
  • 对于那种父子关系的域,其实我们可以使用document.domain,直接将域名改了

    但是,已经弃用了这个特性

    参考链接:Document.domain - Web API 接口参考 | MDN (mozilla.org)

    http://store.company.com/dir/other.htmlhttp://company.com/dir/page.html发送请求,那么会因为domain不相同,不可以请求

    document.domain = "company.com";
    

    那么我们现在就可以通过父域的同源检查了,端口号会另外检查,因为对document.domain操作以后,其实端口号会被重写为null

可能你会对父域名子域名感到疑惑,我的理解是:

父域和子域其实是一个相对概念

sina.com.cnnews.sina.com.cn都是sina.com.cn的子域名,sina.com.cn是父域名

  • 使用jsonp

jsonp仅支持GET请求,其实我们可以这么理解,jsonp就是<script>的特例

因为刚刚我们知道了<script>是可以进行跨域请求的

但是如果我们直接这样写:

<script src="http://169.254.200.238:8020/jsonp.do"></script>

script的jsonp.png

那么确实已经请求成功,但是,因为你放在了<script>里面,但是他会认为你里面的语法是不合法的

jsonp:在服务器返回数据的外边套一层客户端已经定义好callback

//在客户端定义这个函数
<script>
	function callback(data){
		return data
	}
</script>

我们现在来利用ajax来发送一次跨域请求

这里使用了jQuery,因为这个写ajax还是比较方便的

<script>
	$.ajax({
        //请求方式
        type:'get',
        url:'http://192.168.171.220:8080/jsonp.js',
        //标志跨域请求
        dataType:'jsonp',
        //客户端和服务器约定好的回调函数
        //就是你刚刚在那个你需要跨域请求的文件里,包裹的那个函数名
        jsonpCallback:'callback',
        success:function(data){
            console.log("成功",data)
        },
        error:function(err){
            console.log("错误",err)
        }
    })
</script>

jsonp跨域.png

  • CORS

    因为jsonp只限于在GET请求中,并且,在请求和接受的时候都需要进行处理,属于吃力不讨好,所以这时候就出现了CORS

概念:

对于支持CORS请求的浏览器,但面对ajax请求跨域时,会做一些特殊处理,对于已经实现CORS接口的服务器,接受请求,并作出回应

但是对于非简单请求,我们会先进行一次请求类型为OPTIONS的预检请求,来验证是不是服务器允许的源

这里先来说说,什么是简单请求,什么是非简单请求

简单请求和非简单请求:

简单请求:

  • 请求方式:

    • get

    • post

    • Head

  • Head头信息里面的字段

    • Accept
    • Accept-language
    • Content-Language
    • Content-Type: application/x-www-form-urlencoded、 multipart/form-data、text/plain

简单请求只需要CORS服务端在接受到携带Origin字段的跨域请求后,在response header中添加Access-Control-Allow-Origin等字段给浏览器做同源判断。

非简单请求,其实只比简单请求多了一步,就是发送Options类型的预检请求

总结

对于前端跨域其实我觉得,我处理的还是很少的,🤦‍♀️,因为这个大多数都是后端处理好了,但是我们在开发过程中在,其实我也是一般都是使用webpackproxy,这样其实我觉得也挺好的

  • webpackproxy方法
  • document.domain
    • 被弃用了
    • 而且会使端口号变为null
  • jsonp
    • 其实就是一种特殊的<script>
    • 需要一个callback包裹着,并且这个callback是在客户端的
  • cros
    • 会区分简单请求和非简单请求

      • 简单请求不需要预检请求
      • 简单请求需要一个Options类型的预检请求,预检请求的地址和你想要请求的地址相同
    • 其实我对是这个理解还是比较皮毛的,因为都没有自己操作🙄

参考链接:

跨域——CORS详解 - 知乎 (zhihu.com)

jsonp跨域请求详解——从繁至简 - 知乎 (zhihu.com)