跨域的时候,post为什么会发送两次请求?

Daotin 于 2024-08-20 发布 编辑

什么是预检请求?

浏览器在发送“非简单跨域请求”之前,先用一个 OPTIONS 方法,发送一个 空壳请求 到目标服务器,问:“我可以发这个请求吗?”

目的是为了在“请求尚未发出”前,就防止数据泄露或危险操作。

哪些情况会发起预检请求?

只要请求不是“简单请求”,就会触发预检。

  • 同源请求:不需要预检,无论请求多么特殊。
  • 跨源请求:可能需要预检,取决于请求的具体特征。

对于同源请求,即使请求很特殊,也不会触发预检请求。这是因为同源策略(Same-Origin Policy)本身就是一种安全机制,浏览器默认信任来自同一源的请求。

举个具体的例子:

假设你有一个网页游戏,需要向另一个网站的服务器发送玩家的高分数据。

fetch('https://other-website.com/api/scores', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Player-Id': '12345',
  },
  body: JSON.stringify({ score: 1000 }),
});

这个请求会触发预检,因为:

浏览器会先发送一个 OPTIONS 请求(预检请求),询问服务器是否允许这样的请求。如果服务器答应了,真正的 POST 请求才会被发送。

那么,哪些请求会发送预检请求?

浏览器将CORS分为简单请求和非简单请求:

简单请求:

✅ 满足以上就是简单请求,不预检。

预检请求的问题

  1. OPTIONS请求次数过多就会损耗页面加载的性能,降低用户体验度。所以尽量要减少OPTIONS请求次数,可以后端在请求的返回头部添加:Access-Control-Max-Age:number。它表示预检请求的返回结果可以被缓存多久,单位是秒。该字段只对完全一样的URL的缓存设置生效,所以设置了缓存时间,在这个时间范围内,再次发送请求就不需要进行预检请求了。
  2. 浏览器的预检机制并不全面,不会拦住所有危险请求,特别是“简单请求”的 POST / 伪装成简单表单的提交,可以骗过预检,直接操作数据库,造成危险。所以,浏览器的预检机制并不防住所有 POST 请求,尤其是“简单 POST”仍然可以被攻击者利用;真正安全的保障必须依靠后端验证身份、校验来源。

网站开发者需要在服务端做保护,比如:

  1. 使用 CSRF Token:每个表单都带一个随机值,后台验证;
  2. SameSite Cookie 属性:设置 cookie 不允许第三方请求携带
  3. Referer / Origin 验证:服务器检查请求来源是否合法。