你需要掌握的前端基础 - 跨域

2,450 阅读4分钟

什么是跨域

浏览器作为请求方与被请求方的域名、协议、端口三者中有一个不同即被称作跨域。

跨域会导致什么问题

  • ajax 请求被浏览器拦截
  • cookie、Storage、indexDB 等缓存不互通

那么为什么会有跨域呢?那要从同源说起。

什么是同源策略(same origin policy)

两个 url 的域名、协议、端口都相同,称为两个 url 同源。 同源是浏览器的安全限制,默认只有同源的两个网站才能互相获取数据。 如果没有同源限制,不同网站之间可以共享 cookie,攻击者可以直接获取 cookie 发起 CSRF(跨站请求伪造)攻击;接口随便调用,导致数据泄漏或被删。

跨域的解决方案

跨域的解决方案有:script 等标签、JSONP、iframe、node 请求转发、CORS、nginx 反向代理等

img、link、script 标签

img、link、script 标签通过 src 或 href 属性支持访问跨域资源,通常为 cdn 资源、第三方包资源。

<img src="xxx" />
<link href="xxx" />
<script src="xxx"></script>

jsonp

jsonp 算是 script 标签跨域的一种变体,通过在 script 标签的 src 属性后面加上 callback 字段实现跨域请求数据。

  • 客户端
<script>
  function getMenuData(data) {
    // 根据data渲染菜单
  }
</script>
<script src="http://www.a.com/data?callback=getMenuData"></script>
  • 服务端
MenuData({ status: "success", data: ["menu1", "menu2"] });

客户端和服务端在开发阶段做好数据约定,由于它仅支持 get 请求,现在使用场景已经很少了。

iframe

iframe 的 postMessage 方法 天然支持跨域通信,iframe 内外的页面可以采用不同源的 url。

<iframe src="www.b.com"
  ><iframe>
    <script>
      window.addEventListener("message", function (event) {
        console.log(event.data);
      });
    </script></iframe
  ></iframe
>
window.postMessage(message, "http://www.a.com");

nodejs 请求转发

使用 nodejs 创建一个 中转 server, 请求通过访问 node server 转发到目标地址, 突破跨域限制。 从一开始的:

A => C A请求C
A <= C C响应A

转变为:

A => B => C A请求BB请求C
A <= B <= C C响应BB响应A
var koa = require("koa");
var proxy = require("koa-proxy");
var app = koa();
app.use(
  proxy({
    host: "http://www.a.com", // 最终访问的api地址
  })
);
app.listen(3000);

tips:同源策略是浏览器的限制,不是服务器的限制。 此方法常用于开发时,后端不提供跨域支持,由前端解决跨域限制。现在 webpack-dev-server 自带 proxy,不再需要自己启动额外的 server,只用添加配置就行,如下:

    '/api': {
        target: 'https://http://123.123.123.1:8080',
        changeOrigin: true,
        secure: false,
        pathRewrite: {
          '^/api': '/api'
        }
      }

cors

cors 是服务端配置,配置跨域资源请求之后,服务端资源可被跨域请求。主要为以下几个响应头:

  • Access-Control-Allow-Origin 允许访问的域名
  • Access-Control-Allow-Methods 允许请求的方法
  • Access-Control-Allow-Headers 允许请求的 header
  • Access-Control-Allow-Credentials 是否允许携带 cookie

值得注意得是,服务端设置了 CORS 之后需要针对 options 方法的请求做单独处理。

什么是 options 请求

options 请求是浏览器主动泛起的一个刺探请求。 发送简单请求时浏览器不会生成刺探请求,发送复杂请求时会先发送一个嗅探请求,请求方法为 options,待刺探请求成功返回后才发送真正的请求。 嗅探请求的作用是:提前将实际请求所使用的方法和会携带的首部字段发送给服务器,防止对服务器数据产生副作用。 options 请求产生的请求头

origin: http://www.client.com
Access-Control-Allow-Methods: put...
Access-Control-Allow-Headers: Content-type...
  • 简单请求 满足以下条件的请求:

    1. 请求方法为 HEAD、GET、POST 中的一种,

    2. 请求头不超过以下几个:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type:application/x-www-form-urlencoded || multipart/form-data || text/plain
  • DPR
  • Downlink
  • save-Data
  • ViewPort-Width
  • Width
  1. 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器
  2. 请求中没有使用 ReadableStream 对象。
  • 复杂请求 以上简单请求之外的请求

nginx 反向代理

nginx 反向代理是指客户端访问服务器路径 /api 地址时,服务器将 路径/api 下的请求都代理到指定的服务下面,原理和借用 nodejs 实现请求转发是一样的。

    location /api/ {
        proxy_pass http://123.123.123.1:8080/api/;
        index index.html index.htm;
    }

tips

跨域和同源其实不难,主要是要能结合理论解决日常遇到的问题:比如

  1. Access to XMLHttpRequest at 'xxx' from origin 'xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource 你能很快定位问题是服务端没有设置跨域导致的。

  2. Mixed content: the page at 'www.xxx.com/' was loaded over HTTPS, but resquested an insecure prefetch resource 'www.ooo.com/'. This content should also be served over HTTPS 你能很快理解这是浏览器的安全限制。

  3. 是不是所有复杂请求都会发起 options 刺探请求? 不是的,options 请求可以设置缓存时间,在过期之前不会再发刺探请求

Access-Control-Max-Age: time

关于浏览器安全限制、iframe父子页面间通信还有很多内容,欢迎评论、点赞。
此文章为 【前端基础】 系列文章,关注小姐姐,一起学一学!