跨域请求

153 阅读4分钟

平常的工作中,前端向后台发请求的时候,会遇到跨域的问题,这里就跨域问题进行梳理。

同源策略

讲跨域之前,都要讲一下同源策略。

同源策略,是浏览器最核心的安全功能。

同源策略: 同协议 + 同域名 + 同端口

  • 协议:如 HTTP协议
  • 域名:如 binnie.qq.com
  • 端口:如 80

举个例子:http://binnie.qq.com:80http://binnie.qq.com:443 是不同源的,因为其端口不同。

讲完同源策略,就进入到本文的主题了,如果要在不同的源之间进行通信,那就要进行跨域,下面讲一下跨域常用几种方法。

1. JSONP

JSONP,跨域的一种经典模式,核心是使用script标签(因为script标签支持请求其他源的数据)

正常发起js请求是这样的,<script>可以直接拉到js的数据并且直接执行。

<script src="/jquery.js"></script>

JSONP的请求时这样的,请求地址携带callback参数,该参数为数据回调函数,后台回的数据格式为cbFun(res),把回包做为函数的参数返回。

<!-- 前端 -->  
<script>
    function cbFun(res) {
        console.log(res)    // 请求拿到的数据
    }
</script>
<script src="/request?callback=cbFun"></script>

后台也要配合处理,接收请求时获取callback参数,回包时将数据包裹为callback的参数中转为字符串返回。

app.use(async ctx => {
    var res = {
        ret: 0,
        msg: 'ok',
        data: 'Hello World'
    }
    ctx.body = ctx.query.callback + '(' + JSON.stringify(res) + ')'
});

JSONP是跨域的经典操作,当然,jqueryajaxJSONP做了处理,前端请求添加dataType:'jsonp'(数据返回格式)即可发起JSONP请求。

$.ajax({
    url: '/request',
    type: 'GET',
    dataType: 'jsonp',
    success: function(res) {
        console.log(res)
    }
})

当然,因为JSONP是利用<script>标签实现的,所以只能发送get请求。

2. CORS(跨域资源共享)

CORSW3C的标准,通过对response & request headers的设置,允许进行跨域的请求。

CORS请求分为两种:简单请求 & 非简单请求

简单请求

当请求同时符合以下两规则时,请求为简单请求。

  1. 请求类型为:HEAD | GET | POST
  2. Content-Type:text/plain | multipart/form-data | application/x-www-form-urlencoded

发送CORS请求的时候,浏览器会在request headers增加一个Origin字段,这个字段就是网站的源(协议+域名+端口)

Origin: 'http://binnie.qq.com'

服务器跟Origin对应的是 Access-Control-Allow-Origin,该属性表示服务器支持的源是哪些,也可以是*全部支持。

Access-Control-Allow-Origin: 'http://binnie.qq.com'

由于请求是跨域的,所以cookie是携带不过去的,如果想携带本域的cookie,请求中就要多带一个字段 withCredentials

withCredentials: true

服务器跟withCredentials对应的是Access-Control-Allow-Credentials,表示是否允许携带cookie。如果需要添加这个配置,那么Access-Control-Allow-Origin不允许为*,只能设置为确定的域名

Access-Control-Allow-Origin: 'http://binnie.qq.com'
Access-Control-Allow-Credentials: true

非简单请求

不符合简单请求的,就都是非简单请求。

非简单请求与简单请求不不同之处在于,简单请求一条请求就搞定,非简单请求需要两条。

  • 第一条:预检请求(OPTIONS)
  • 第二条:真正的请求

预检请求

预检请求,作用是先询问服务器,之后的请求的域、请求类型、携带headers有哪些,服务器通过之后,就会发送真正的请求,服务器不通过则不会发真正的请求。

预检Headers会携带以下几个信息

Origin: 'http://binnie.qq.com'              // 源
Access-Control-Request-Method: 'PUT'        // 真正请求的方法
Access-Control-Request-Headers: 'binnie'    // 有扩展Header

服务器与预检请求的对应

Access-Control-Allow-Origin: 'http://binnie.qq.com'     // 允许的源
Access-Control-Allow-Methods: 'GET,POST,PUT'            // 允许的请求方法
Access-Control-Allow-Headers: 'binnie'                  // 允许的扩展Header

真正的请求

预检请求通过之后,其实真正的请求就可以请求成功了,这时request Headers携带Origin字段。

CORSJSONP的对比这里也可以看出来,CORS支持的请求类型更加丰富。

3. postMessage

postMessageHTML5的新特性,支持iframe之间互相发送信息,由于iframe之间可以不同域,所以postMessage也是跨域的一种实现。

4. window.name

window.namepostMessage的跨域其实是同种类型,在页面中,父页面与iframe都是共享同一个window.name的,并且都有读写权限。

5.document.domain

document.domain可以设置为父域,但是不能设置为其他。

6.webSocket

webSocket,浏览器不对其做同源限制,所以直接就可以跨域。

写在最后

前后台对接的时候,语气好的话可能不用跨域,当然跨域的问题也经常会出现,熟悉之后会发现跨域请求还是很容易实现的。