CORS跨域预检
简单记录一下CORS跨域预检踩得坑。
0、搜索匹配
- 前端莫名自动发起两次请求,而代码中只请求了一次。
- 前端自动发起OPTIONS请求。
- 后端设置了
Access-Control-Allow-Origin
允许跨域,前端仍CORS error
跨域错误。
1、背景
前端页面地址:http://localhost:4001
后端接口地址:http://localhost:8080
前端跨域请求后端,方式的GET,请求头中添加了我们自定义的名为token
的header,请求情况大致如下:
GET http://localhost:8080/login.action
headers:
KEY | VALUE |
---|---|
省略默认的HTTP标准头 | … |
Token | 002e9b401eca4f28b4221dc141541e3f |
对应的大致代码如下:
let url = "http://localhost:8080/login.action";
const headers = new HttpHeaders({ "Token": "002e9b401eca4f28b4221dc141541e3f" });
this.httpClient.get<any>(url, { headers: headers }).subscribe({
next: (v) => { console.log("成功了") },
error: (e) => { console.log("失败了") },
complete: () => { console.log("完成了") }
});
2、问题
表现是请求未成功。F12开发者选项检查网络请求,发现前端发起了两次接口请求,请求方式分别是OPTIONS
和GET
,但是前端代码中确实只写了一次请求。
其中OPTIONS
请求的结果是 200 ,GET
请求的结果是 CORS error 。
后端Debug调试发现,第一次请求是OPTIONS
,且请求头里面并没有 Token ,断点继续往下走,后端没有抛出异常,成功响应了“ 200 [OK] ”。
诡异的是,在我后端断点中断的时候,前端F12开发者工具中看到两次请求都是 pending (表示请求阻塞中,服务器还未响应请求结果),当我放开断点,两个请求同时有了结果,即一个 200[OK] ,一个 CORS error 跨域错误。且后端并未收到第二次请求(即应为GET方式的请求)。
3、分析
经查阅资料发现,这种两次请求的行为叫 【CORS跨域预检 / CORS Preflight】 ,满足某些条件的请求将会触发该行为,具体规则见尾部附加的参考资料。个人理解是,OPTIONS请求完成后浏览器会检查响应结果(尤其是response header响应头),对于不符合条件的响应结果浏览器将直接按CORS error跨域错误来处理(并不会发起正式请求),对于符合条件的响应结果才会正式发起请求(即本文中的GET请求)。
4、解决
解决方法是在响应体中添加满足CORS预检条件的response header响应头(仅列出必须携带的响应头):
Access-Control-Allow-Origin: https://your.domain.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Allow-Headers: X-Custom-Header, My-DIY-Header
讲解:
-
Access-Control-Allow-Origin
头是大家最熟悉的允许跨域的站点,许多懒省事的开发者会将其不安全的设置为Access-Control-Allow-Origin: *
,即允许所有站点的跨域请求。 -
Access-Control-Allow-Methods
是大多数查询CORS跨域预检资料时所被提到的,本文中我想要正式请求的方式是GET,因此该响应头的值中至少应包含GET,即Access-Control-Allow-Methods: GET
。该值可以为多种请求方式,中间用英文逗号分隔。 -
Access-Control-Allow-Headers
便是我被坑到的地方,大多数资料都会提到上面的请求方法,但很少有提到这个响应头。由于我添加了叫Token
的自定义请求头,但我的后端并没有在响应头中添加Access-Control-Allow-Headers: Token
,所以导致前后端一直跨域错误,加上这个就解决了。这个响应头的值和上面那个一样,可以为多个值,中间英文逗号分隔。
5、参考
Source: https://docs.ximinghui.org/751083ef19c8.html