CORS与预检请求

CORS

出于安全性,浏览器限制脚本内发起的跨源 HTTP 请求。例如,XMLHttpRequest 和 Fetch API 遵循。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。

跨源资源共享CORS,通过允许服务器标示除了它自己以外的其他访问源、协议或端口,使得浏览器允许这些源访问加载自己的资源。简单讲,就是目标资源与当前页面不处于同一个域时,浏览器的同源策略会阻止请求发出者获得响应数据。除非服务器允许跨域访问,也就是支持CORS。

这是一些CORS相关的响应头信息,如果目标资源服务器支持CORS,必须附带一部分这些头信息与浏览器交互

Access-Control-Allow-Origin:该字段必须出现在服务器的响应中,表示允许跨域的源。如果是通配符( * ),表示接受任意域名的请求。
Access-Control-Allow-Methods:该字段表示接受的请求方法列表,多个值用逗号分隔。
Access-Control-Allow-Headers:该字段指定了实际请求可以携带的头部信息,多个值用逗号分隔。
Access-Control-Allow-Credentials:该字段标记是否允许发送 Cookie。如果为 true,则表示允许发送 Cookie。
Access-Control-Max-Age:该字段指定了预检请求的有效期(单位为秒),即在指定时间内不再发送预检请求,直接发送实际请求即可。
Access-Control-Expose-Headers:该字段用于指定哪些头部信息可以作为响应的一部分被获取到。

预检请求

当发送跨域请求时,浏览器会先发送一个OPTIONS方法的预检请求,探测服务器是否允许实际请求(POST、GET等)的跨域访问。服务器在接收到这个请求后,会返回一组响应头信息(详见上文),包含了对CORS的支持情况和允许的请求方法,如果服务器对跨域请求进行了允许,则浏览器才会真正地发送POST请求。如果服务器对预检请求的响应头中拒绝跨域访问或者根本没有任何CORS头信息,浏览器会抛出错误

因此,浏览器需要发送两次请求来确认服务器是否允许跨域请求,并确保安全性。

下面将演示触发预检请求的情况并且演示是处于跨域的环境下。

携带了自定义头信息的请求

在这种情况下,无论任何请求方法都会触发预检请求

我们请求 https://api.github.com/,这个接口支持跨域访问,我们通过添加自定义请求头,来触发预检请求

代码:

var requestOptions = {
  method: 'GET',
  headers: {
    test: 'test'   // 自定义的头信息
  },
  redirect: 'follow'
}

fetch("https://api.github.com/", requestOptions)
  .then(response => response.json())
  .then(result => console.log(result))
  .catch(error => console.log('error', error))

运行效果:

CORS与预检请求

可以看到明明在代码里面只有一次请求,可是浏览器却发出了两次请求,其中一个请求就是预检请求

看下预检请求和真实请求的请求头:

CORS与预检请求

CORS与预检请求

不难发现

  1. 真实请求中,携带了自定义请求头字段
  2. 在预检请求头中,access-control-request-headers字段标识了真实请求头中的自定义字段
  3. 在预检请求头中,access-control-request-method字段标识了真实请求所使用的方法,这里是GET方法

总结来说,预检请求会携带上真实请求使用的请求方法和自定义头字段,去探测服务器是否允许这样做。

再来看下预检请求的响应头

CORS与预检请求

这些用蓝色方框圈起来的字段可以在上文中找到对应的解释。这里需要说明一点,可以从第一张图看到真实请求是失败的,原因在于我们添加的自定义请求头字段test并不在服务器允许的请求头字段中,这一点从预检请求的响应头字段access-control-allow-headers很容易看出。

那么如果不携带test头信息,或者换成允许的头字段,请求会成功,这一点留给读者自行测试。

PUT,DELETE方法的请求

PUT请求

var raw = JSON.stringify({
  "selected_organization_ids": [
    32,
    91
  ]
})

var requestOptions = {
  method: 'PUT',
  body: raw,
  redirect: 'follow'
}

fetch("https://api.github.com/enterprises//actions/runner-groups//organizations", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error))

CORS与预检请求

DELETE请求

var requestOptions = {
  method: 'DELETE',
  redirect: 'follow'
}

fetch("https://api.github.com/admin/users//authorizations", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error))

CORS与预检请求

真实请求失败是因为没有传递正确的参数或者接口需要认证。

服务器不允许跨域

上文的演示都是建立在目标资源允许跨域访问的基础上,下面演示目标资源不允许跨域的情况

fetch("https://buy.vmall.com/getSkuRushbuyInfo.json", {
  method: 'get',
  headers: {
    test: 'test'
  }
}).then(res => {
  return res.json()
}).then(res => {
  console.log(res)
})

CORS与预检请求

CORS与预检请求

观察到预检请求头中没有任何CORS响应头信息,表示服务器不允许跨域访问,这时候浏览器就会报错

CORS与预检请求
在代码层面也是可以捕捉到错误的,但是演示代码中没有捕获。

总结

  1. CORS是一套规范,指导浏览器和服务器之间如何进行跨域资源共享
  2. 如果请求跨域,对于一些特定类型的请求,浏览器会先发送一次预检请求,去探测服务器是否允许
  3. 演示了一部分会触发预检请求的情况
  4. 更多关于CORS的规范可以参考:CORS W3C规范跨站点HTTP请求 (Cors) | MDN (mozilla.org)

不断学习,后续可能会补充内容,水平有限,欢迎大家在评论区一起讨论,感谢。

原文链接:https://juejin.cn/post/7224903881729753148 作者:大田稻谷

(0)
上一篇 2023年4月23日 上午10:00
下一篇 2023年4月23日 上午10:10

相关推荐

发表回复

登录后才能评论