前端CORS跨域规范、解决方案

107 阅读4分钟

一、什么是浏览器的同源策略

  1. scheme http、https
  2. host 域名、ip
  3. port 端口号

如果scheme://host:port都相同,那么就是同源,否则是跨域CORS

跨域请求一般指的是AJAX异步请求,比如XHR和fetch,其他以此为底层请求的都属于此类(比如axios)。

src加载的img资源不属于AJAX,因此不受同源策略限制,JSONP库就是利用这个特点封装请求。

不经过浏览器发起AJAX请求不受CORS的限制,因为CORS是浏览器的限制。

二、跨域的访问

第一种:满足简单跨域,浏览器允许发出请求

条件 1.请求方法属于下面的一种(简单的两种请求方式):

  • 。get
  • 。post|
  • 。head

条件 2. 请求头仅包含安全的字段,常见的安全字段如下(也就是不自己添加自定义请求头):

  • 。Accept
  • 。Accept-Language
  • 。Content-Language
  • 。Content Type
  • 。DPR
  • 。Downlink
  • 。Save-Data
  • 。Viewport- width
  • 。Width

条件 3. 请求头如果包含Content-Type,仅限下面的值之一(也就是简单的form表单请求):

  • 。text/plain
  • 。multipart/form-data
  • 。application/x-www form- urlencoded

对于简单跨域请求,浏览器端运行放行,这个请求得以发给服务器端

服务器端根据自己的CORS策略,设置响应头:Access-Control-Allow-Origin: *,表示同意所有跨域请求获取资源。

浏览器收到服务器端的同意跨域后,才接收当前请求的响应数据。

总结:对于简单跨域,浏览器端不用设置请求头,服务器端需要设置Access-Control-Allow-Origin: *或指定Origin

从上面就可以看出来,CORS就是浏览器为了自己安全着想搞出来的蛾子;

首先,对于不符合CORS跨域的请求,直接不给发出去,根本没服务器端的事儿;

对于符合的跨域,发出请求,让服务器决定是否可以跨域,如果服务器不同意,浏览器也会禁止自己拿数据。

一般遇到的错误:CORS header 'Access-Control-Allow-Origin' missing

解决办法:在服务器端添加Access-Control-Allow-Origin

const allowOrigin = ["http://localhost:9000"];
let origin = req.headers["origin"];
if (allowOrigin.includes(origin)) {
    resp.setHeader("Access-Control-Allow-Origin", origin);
}

第二种:需要预检的跨域请求

此类请求稍微更加敏感了,因为:

比如:请求头的Content-Type改为了application/json,等等其他超越简单请求的改动。

这类请求,浏览器端不会(不敢)直接发送请求体的数据,而是先发送一个预检请求(method=Options)

然后服务器端返回响应头信息,浏览器会检查响应头中的:请求改动的东西是否被允许

比如浏览器AJAX设置了Content-Type改为了application/json,那么浏览器就会检查响应头中有无:Access-Control-Allow-Headers: Content-Type

说白了,还是浏览器自己检查自己,

报错参考:developer.mozilla.org/zh-CN/docs/…

解决办法:在服务器端添加:

    resp.setHeader("Access-Control-Allow-Origin", "具体的跨域源origin");
    
    resp.setHeader("'Access-Control-Allow-Method", "*");
    resp.setHeader("Access-Control-Allow-Headers", "*");

直接放通所有,只做跨域的源检查即可。

CORS只是限制浏览器端跨域获取资源,并不能防止非浏览器获取服务器资源

禁止非法访问资源应该使用服务器端密码校验等其他手段。

第三种:携带cookie等, 的跨域访问(withCredentials: true

AJAX请求如果设置了withCredentials: true,那么

服务器端,需要在响应头中添加:Access-Control-Allow-Credentials: true

要特别注意的是:对于附带身份凭证的请求,服务器不得设置Access -Control-Allow-Origin的值为*

解决办法:在服务器端添加:

    resp.setHeader("Access-Control-Allow-Origin", "具体的跨域源origin");
    
    resp.setHeader("Access-Control-Allow-Method", "*");
    resp.setHeader("Access-Control-Allow-Headers", "遍历前端传过来的,,,");
    
    resp.setHeader("Access-Control-Allow-Credentials", "true");
    
   resp.setHeader("Access-Control-Expose-Headers", "Authorization,Abc,具体");

注意:Access-Control-Expose-Headers

告诉浏览器要不要把除了基本的header之外的其他自定义header暴露给前端AJAX代码获取,

比如服务器想给前端传Header:

resp.setHeader("Authorization", "123");

一般网站会使用header(比如header-token:KJDDKND...)cookie同时做身份校验

所以服务器端只要指定允许的跨域请求的源即可,其他放开


二、跨域的解决方案

  • 1、反向代理:把前端、后端同时代理到同一个域名下
  • 2、JSONP,利用src请求资源不受同源策略影响的漏洞(未测试过)
  • 3、服务器设置允许跨域请求(如果要携带cookie,前端还要设置withCredentials: true