这是一篇关于浏览器跨域的文章

97 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

为什么会有跨域

由于浏览器的同源策略,浏览器会拒绝跨域请求

同源

浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。所以,当域名、协议、端口都相同时,视为属于同域。同域之间相互请求资源,就是同源。

同源策略: 协议(http https) 端口 主机 任何一个不一样都会产生跨域

JSONP

大家都知道 HTML 中的 script 标签可以用来加载其他域线上的数据当做 JS 代码来执行 , 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。
换句话说,JSONP 需要对应接口的后端的配合才能实现。

看个例子

<script>
function json(a){
console.log(a);
}
</script>
<script src="http://a.com?callback=json"></script>

CORS

CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式。 当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:

Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。

简单请求和复杂请求

  • 简单请求
    请求方法:get post head
    除了以下请求头没有自定义请求头:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width

    Content-Type 的值只有以下三种(Content-Type 一般是指在 post 请求中,get 请求中设置没有实际意义)

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded 简单请求不会进行预请求
  • 预请求

    “需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

    服务器检查了预请求请求头确认是否允许跨域,预请求是不会携带请求参数

  • 复杂请求

    非简单请求就是复杂请求,加入了一些自定义的请求也是复杂请求 复杂请求每次请求都会先预请求,

  • Nginx 配置跨域

worker_processes  1;
events {
    worker_connections  1024;
}
http {
  ... 省略代码

  # 跨域
  add_header Access-Control-Allow-Origin *;
  add_header Access-Control-Allow-Headers X-Requested-With;
  add_header Access-Control-Allow-Methods GET,POST,OPTIONS;

  ... 省略代码
}

postMessage

Window.postMessage API 的功能是允许程序员跨域在两个窗口/frames 间发送数据信息。基本上,它就像跨域的 ajax,但是它不是浏览器和服务器之间的交互,它是两个客户端之间的通信。

postMessage 方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本文档、多窗口,跨域消息传递。

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

实际前端处理跨域的常见比较少见,基本上是后端处理 根据 CORS 添加相应的源

开发环境配置跨域

在开发环境如果遇到跨域问题,并且服务端不帮我们解决,那么我们可以自己设置 一般我们的项目 使用 webpack 或者是基于 vue-cli 的脚手架项目,都是可以设置代理

基本原理是使用了 http-proxy-middleware http 代理的中间件 ,之前我们的请求是直接发起服务器请求,现在我们设置了代理,相当于中转了一层,发起请求到 HTTP 代理,由中间层帮我们发起请求,(中间层发起请求会替换喂服务器的域名) 所以客户端和服务端就相当于在同一个域名下,不会由跨域问题

devServer: {
    proxy: 'http://localhost:4000'
}
// 或者
devServer: {
    proxy: {
      '/api': {
        target: '<url>',
        ws: true,
        changeOrigin: true
      },
      '/foo': {
        target: '<other_url>'
      }
    }
  }

这种方法只能用开发环境,正式环境的话需要使用 nginx