likes
comments
collection
share

2023-06-20 从根本理解浏览器的同源策略及跨域方案

作者站长头像
站长
· 阅读数 12

前言

目前我们很多前端开发程序员在处理跨域问题的时候,大部分都不清楚跨域是怎么发生的,以及到底什么是跨域,本文将会一步一步的解开这些迷惑

跨域产生的前提原因

跨域这一问题其实都是浏览器搞出来的,脱离了浏览器的限制,根本不存在跨域问题。要知道,只有浏览器发出请求才会产生跨域问题,而两个服务端通信发出请求根本不会发生跨域,这是因为浏览器为了安全问题才搞出了同源策略

那什么是同源策略呢?

同源策略

浏览器出于安全考虑,对同源请求放行,简单的说,就是自己人;对异源请求限制即不是自己人的请求,这些限制规则统称为同源策略。因此限制造成的开发问题,称之为跨域(异源)问题

何为同源?

2023-06-20 从根本理解浏览器的同源策略及跨域方案

协议、域名和端口三个都一样的为同源,否则是异源

源1源2是否同源
http://a.com:81/ahttps://a.com/a
http://a.com:81/ahttp://www.a.com:81/a
http://a.com:81/ahttp://a.com:82/a
http://a.com:81/ahttp://a.com:81/a/b

上述第一行是两个源的协议不同,是为异源;

第二行是域名不同,也为异源;

第三行是端口不同,异源;

最后一行是协议、域名和端口都相同,路径不同是为同源。

何为同源请求?

2023-06-20 从根本理解浏览器的同源策略及跨域方案

同源请求不止针对Ajax,对其他类型的如请求图片、JS文件、CSS等,只不过对Ajax的限制较为厉害而已

浏览器如何限制?

对标签元素发出的跨域请求轻微限制

Ajax发出的跨域请求严厉限制

对Ajax的限制如图:

2023-06-20 从根本理解浏览器的同源策略及跨域方案

这里跨域的发生地方在收到响应时;浏览器是发出请求了的,同时服务端也接收到了请求,而且返回了响应,只不过这个响应被浏览器拦截了,因为有安全因素考虑,会对响应做出校验,通过了就没有跨域问题了,没有校验通过就会引发错误产生跨域问题了

比如在浏览器控制台发出一个异源请求,就会发生错误

2023-06-20 从根本理解浏览器的同源策略及跨域方案

跨域的三大基本解决方案

一是CORS(Cross-Origin-Resource Sharing)-跨域资源共享

2023-06-20 从根本理解浏览器的同源策略及跨域方案

如上述图所示,,跨域发生的原因就是在这个校验规则上,校验通过了就不会发生跨域问题,校验不通过就会引发跨域问题,跨域的校验规则就是CORS规则,也是跨域产生的最本质的地方

跨域的解决方法有很多种,但是CORS一定是最根本、最正统的方法

CORS是一套机制,用于浏览器校验跨域请求

它的基本理念是:

只要服务器明确表示允许,则校验通过

服务器明确拒绝或没有表示,则校验不通过

所以这里的解决主要靠服务器,还得保证服务器是自己人,能访问修改

CORS将请求分为两类:简单请求预检请求

简单请求
  • 请求方法为:GET、HEAD、POST

  • 头部字段满足CORS安全规范,详见:MDN

  • 请求头的Content-Type为:

    • text/plain(表示请求体中是纯文本数据,可以用普通的纯文本编辑器查看与编辑)
    • multipart/form-data(表示请求体中包含多种数据类型,如文件上传等,通常用于表单提交)
    • applocation/x-www-form-urlencoded(表示请求体中包含键值对数据,以键值对的形式拼接在URL后面发送请求,也可放在请求体中)

这里注意只要不去改动请求头部字段,则基本都是满足安全规范

预检请求

既是非简单请求

举两个例子:

fetch('https://douyin.com')

2023-06-20 从根本理解浏览器的同源策略及跨域方案 这是个简单请求

fetch('https://douyin.com', {headers:{a:1}})

2023-06-20 从根本理解浏览器的同源策略及跨域方案 这是预检请求,因为改了头部字段

知道了简单请求和预检请求后,那么浏览器遇到这两种请求是如何校验的呢?

针对简单请求和预检请求的校验

简单请求:

2023-06-20 从根本理解浏览器的同源策略及跨域方案

这里浏览器发现是简单请求后,会自动在请求头headers中添加Origin这个字段,表示是来自哪个源的请求,然后服务器一般会在返回响应头中添加Access-Control-Allow-Origin字段,如果值和请求源一样,浏览器一看表示允许这个源通过,则校验通过;或者值为*号,表示所有源都可以通过,那浏览器一看不管了,都校验通过

预检请求:

2023-06-20 从根本理解浏览器的同源策略及跨域方案

2023-06-20 从根本理解浏览器的同源策略及跨域方案

当浏览器发现是预检请求后,会先发出一个OPTIONS的预检请求,请求头中带有Origin-请求源地址,Access-Control-Request-Method-请求方法和Access-Control-Request-Headers-这次请求改动的头部字段等,服务器收到后会返回响应头字段有允许的源地址、允许的方法、允许的头部headers和最大缓存时间,下次这个时间范围内直接使用缓存

当这个OPTIONS请求通过后才会发出真实的请求和简单请求处理一模一样

二是古老的JSONP方案

在没有CORS方法的年代,JSONP就是最佳方法

在同源策略中,对标签的跨域请求限制较小,JSONP就利用了这一点

2023-06-20 从根本理解浏览器的同源策略及跨域方案

与Ajax毫无关系,要求服务器响应一段JS代码,举个例子如下:

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <title>跨域方法之JSONP</title>
  </head>
  <body>
    <button>点击获取用户</button>
    <script>
      function callback(resp) {
        console.log(resp);
      }

      function request(url) {
        const script = document.createElement("script");
        script.src = url;
        script.onload = function () {
          script.remove();
        };
        document.body.appendChild(script);
      }

      document.querySelector("button").onclick = function () {
        request("http://localhost:8001/jsonp?callback=callback");
      };
    </script>
  </body>
</html>

用node写一个简单的服务器

const express = require("express");
const app = express();

app.get("/jsonp", (req, res) => {
  const { callback } = req.query;
  res.send(`${callback}("这是后台响好数据")`);
});

app.listen(8001, () => {
  console.log("欢迎访问http://localhost:8001");
});

点击按钮请求不是Ajax而是一个js

2023-06-20 从根本理解浏览器的同源策略及跨域方案

2023-06-20 从根本理解浏览器的同源策略及跨域方案

注意:JSONP有个致命的缺陷,因为其是标签发出请求,只能是get请求,而如post等方式的请求处理不了

三是跨域方案之代理

上述的两个方案CORS和JSONP均对服务器有要求

2023-06-20 从根本理解浏览器的同源策略及跨域方案

简单来说就是要求服务器是自己人,一旦不是自己人,无法修改服务器则这两种方案都失效了

这时候就需要用到代理方案了,找一个中间代理人

2023-06-20 从根本理解浏览器的同源策略及跨域方案

这里浏览器要想请求不是自己人的服务器,则首先把请求地址发送到自己人的服务器上,自己人的服务器再把这个请求转发到目标服务器上,而两个服务器之间的请求是没有跨域的,脱离了浏览器的范畴了,所以自己人服务器正常接收到目标服务器的响应,再把这个响应转发给浏览器,这时候的跨域问题就非常简单明了了用CORS或者JSONP都可以

下面是一个例子:本地请求王者荣耀的接口

2023-06-20 从根本理解浏览器的同源策略及跨域方案

肯定是跨域的了,但是其服务器不是自己人,不能改,这时候前端可以用node写一个代理服务器

2023-06-20 从根本理解浏览器的同源策略及跨域方案

首先写一个node服务器来验证服务器之间的请求是不存在跨域问题的

2023-06-20 从根本理解浏览器的同源策略及跨域方案

然后只要把这段代码运用自己人的服务器上并且用CORS方案即可

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>跨域方案之代理</title>
  </head>
  <body>
    <button>点击请求代理服务器</button>
    <script>
      const btn = document.querySelector("button");
      btn.onclick = function () {
        fetch("http://localhost:9100/hero")
          .then(resp => resp.json())
          .then(res => {
            console.log(res);
          });
      };
    </script>
  </body>
</html>

const axios = require("axios");
const express = require("express");
const app = express();

// get请求
app.get("/hero", async (req, res) => {
  // 请求王者荣耀的接口
  const resp = await axios.get("https://pvp.qq.com/web201605/js/herolist.json");

  // 使用CORS解决代理服务器的跨域
  res.header("Access-Control-Allow-Origin", "*");
  // 转发响应
  res.send(resp.data);
});

// 监听
app.listen(9100, () => {
  console.log("代理服务器启动了, http://localhost:9100");
});

2023-06-20 从根本理解浏览器的同源策略及跨域方案

跨域方案的总结

有如下决策图:

2023-06-20 从根本理解浏览器的同源策略及跨域方案 当遇到服务器不是自己人不能更改服务器的时候,就只能用代理方法了;

当是自己人但是古老的浏览器不支持CORS的话就选择JSNOP方法,剩下的最佳方法是CORS。

一个经典的开发场景:

公司中开发项目,服务端的部署图:

2023-06-20 从根本理解浏览器的同源策略及跨域方案

上图中的nginx服务器一般不提供CORS和JSONP

生产环境下:

    `http://a.com/index.html` 请求的HTML页面
    `http://a.com/api/movie` 请求的接口
    二者之间不存在跨域问题,同源

开发环境下:

    `http://localhost:8080/index.html` HTML页面
    `http://api.a.com.api/movie` 接口
    异源请求,跨域了
    这时候要用代理了,如dev-server启动本地服务器用作转发代理,原理同CORS
    `http://localhost:8080/api/movie` 接口
    让代理服务器与nginx服务器交互即可

完结撒花~~