# HTTP跨域

前后端数据交互经常会碰到请求跨域,什么是跨域,以及有哪几种跨域方式,这是本文要探讨的内容。

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

# 什么是跨域?

由于浏览器同源策略,当前端通过ajax向后端发起请求时,当协议,域名,端口中有一个不同就会发生跨域。

协议 子域名 主域名 端口 资源地址
http:// www baidu.com :80 index.html

# 同源策略

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截了

# 解决方案

# 方案一 Jsonp

利用 script 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP 请求一定需要对方的服务器做支持才可以。

前端动态创建script标签作为前端请求,后端返回的数据用 callback的name包裹进行返回

前端代码示例

DETAILS
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<button id="send">send</button>
<script>
  function show(data) {
    console.log('data :>> ', data);
  }
  $('#send').click(function() {
    $.ajax({
        url: "http://192.168.34.168:3000/jsonp",
        dataType: "jsonp",
        type: "get",//可以省略
        jsonpCallback: "show",//->自定义传递给服务器的函数名,而不是使用jQuery自动生成的,可省略
        jsonp: "callback",//->把传递函数名的那个形参callback,可省略
        success: function (data) {
          console.log('data :>> ', data);
        }
      });
  })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

后端代码示例

DETAILS
// 后端服务
const Koa = require('koa');
const app = new Koa();
const Router =require('@koa/router')
const router = new Router();
router.get('/jsonp', async function(ctx, next) {
    ctx.body = `show({name:1,age:2,test:34243})`
})
app.use(router.routes())
app.listen(3000,'0.0.0.0');
1
2
3
4
5
6
7
8
9
10
  • 优点:JSONP 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。
  • 缺点:仅支持 get 方法具有局限性,不安全可能会遭受 XSS 攻击。

# 方案二 CORS 跨域资源共享

CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 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

# 复杂请求

复杂请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。

put delete 等复杂请求在跨域的时候需要同时设置下面这2个

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods

# 方案三 postMessage

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

它可用于解决以下方面的问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间消息传递
  • 页面与嵌套的 iframe 消息传递

# 方案四 websocket

Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket 和 HTTP 都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

# 方案五 Node 中间件代理(两次跨域)

同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略

前端向同源服务器发送请求,代理服务向目标服务进行请求转发,再次将相应结果返回目标前端。

  • 代理服务接受客户端请求 。
  • 代理服务将请求 转发给目标服务器。
  • 代理服务拿到目标服务器 响应 数据。
  • 代理服务将 响应 转发给客户端。

3t358kx820.png

# 方案六 nginx 反向代理

实现原理类似于 Node 中间件代理,需要你搭建一个中转 nginx 服务器,用于转发请求。

使用 nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。

实现思路:通过 nginx 配置一个代理服务器(域名与 domain1 相同,端口不同)做跳板机,反向代理访问 domain2 接口,并且可以顺便修改 cookie 中 domain 信息,方便当前域 cookie 写入,实现跨域登录。

# 方案七 window.name + iframe

# 方案八 location.hash + iframe

# 方案九 document.domain + iframe