如何解决浏览器跨域(CORS)问题

简介

浏览器同源策略(same-origion policy)

同源策略是Netscape公司在1995年引入浏览器的一个著名安全策略,它是浏览器最核心也最基本的安全功能,可以概括为本域脚本只能读写本域内的资源,而无法访问其它域的资源,以防止信息泄露。例如A, B两个网站属于两个不同的域(比如www.a.com和www.b.com),A网站中的JavaScript脚本就只能访问A网站的资源,而不能访问B网站的资源,因为同源策略的限制使得跨域访问是会被浏览器拒绝的。

所谓同源是指域名相同、协议相同且端口相同。举例如下:

  • 不同域名:http://www.baidu.comhttp://www.baidu.cn为不同源;
  • 不同协议:http://www.baidu.comhttps://www.baidu.com为不同源;
  • 不同端口:http://www.baidu.com:80http://www.baidu.com:81为不同源;
  • 其它:http://www.baidu.com/ahttp://www.baidu.com/b为同源,因为域名协议和端口都相同。

什么是跨域资源共享(CORS)

在实际应用中会经常遇到跨域访问的情况,例如,用户的网站A(www.a.com)后端使用了BOS存储,用户想在该网站的Web应用程序中引用存储在BOS上的资源,但该页面只能请求本域资源,向BOS发送的请求会被浏览器限制,无法直接访问带来不便。为了解决这类跨域访问问题,HTML5提供了一套标准跨域解决方案即CORS。

跨域资源共享(Cross-Origin Resource Sharing,简称 CORS),它是由浏览器共同遵循的一套控制策略,通过HTTP的Header来进行交互。浏览器在识别到发起的请求是跨域请求的时候,会将Origin的Header加入HTTP请求发送给服务器,比如上面那个例子,Origin的Header就是www.a.com。服务器端接收到这个请求之后,会根据一定的规则判断是否允许该来源域的请求。如果允许的话,服务器在返回的响应中会附带上Access-Control-Allow-Origin这个Header,内容为www.a.com来表示允许该次跨域访问。如果服务器允许所有的跨域请求的话,将Access-Control-Allow-Origin的Header设置为*即可,浏览器根据是否返回了对应的Header来决定该跨域请求是否成功,如果没有附加对应的Header,浏览器将会拦截该请求。

CORS需要浏览器和服务器同时支持,整个CORS通信过程,都是浏览器自动完成,不需要用户参与。浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息,有时还会多出一次附加的请求。只要服务器实现了CORS接口,就可以跨域通信。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

简单请求是指请求方式是HEAD/GET/POST三种方式中的某一种,而且http头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

不符合以上条件的则是非简单请求。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP OPTIONS查询请求,称为"预检"请求, 请求头信息里包括Origin、Access-Control-Request-Method和Access-Control-Request-Headers三个特殊字段,服务器收到"预检"请求以后,检查了三个字段以后,确认允许跨源请求,就可以做出回应。服务器的回应,都会有一个Access-Control-Allow-Origin头信息字段。

BOS配置CORS

BOS为开发者提供了两种方式来配置bucket资源的跨域访问权限,一种是直接在BOS控制台对bucket进行CORS规则设置;另一种是调用CORS相关的API接口来控制bucket资源的访问权限。

注意:

  • BOS中CORS配置是在Bucket级别的;
  • CORS请求是否通过和BOS的身份验证是完全独立的,因为CORS规则仅仅是用来决定是否附加CORS相关的Header的一个规则,是否拦截该请求完全由浏览器决定。
  • 控制台设置方法:

    1. 点击“基础配置”页签,选择“跨域访问CORS设置”并点击“修改配置”。
    2. 点击“确定”,保存规则

    image.png

  • API控制方法:
    1. PutBucketCors接口: 用来在指定的Bucket上设定一个跨域资源共享(CORS)的规则,如果原规则存在则覆盖原规则。
    2. GetBucketCors接口: 用于获取指定的Bucket当前的CORS规则。
    3. DeleteBucketCors接口: 用于关闭指定Bucket的CORS功能并清空所有规则。
    4. OPTIONS Object接口: 浏览器在发送跨域请求之前会发送一个preflight请求(OPTIONS)并带上特定的来源域,HTTP方法和Header信息等给BOS以决定是否发送真正的请求,此接口即响应这种请求。

实战案例

以下我们会详细展示简单请求和非简单请求的跨域访问BOS资源时的情况。其中简单请求以GET请求为例, 非简单请求以POST请求为例。

准备条件

  1. 登录BOS控制台新建一个Bucket,读写权限设置为“公共读写”,然后上传一个Object。在本例中,我们新建一个名为bos-demo的Bucket,然后上传一个bos.txt文件,文件内容为“This is a bos demo for CORS test !”。点击“复制链接”,可以看到bos.txt这个object的访问地址: http://bos-demo.bj.bcebos.com/bos.txt。

  2. 关闭浏览器cache功能,防止因为浏览器缓存了服务器上次返回的header内容导致和CORS的要求不匹配,影响请求结果,这里我们以chrome浏览器为例, 打开“开发者工具”,勾选“Disable cache”。

跨域请求实例

  1. 首先用curl访问bos.txt文件,可以发现该object已经可以正常访问。
    curl http://bos-demo.bj.bcebos.com/bos.txt  
    This is a bos demo for CORS test !
    
  2. 接着我们尝试使用Fetch API来访问这个object,可以将以下代码复制到本地保存成html文件然后使用浏览器打开,代码中提供了发送GET和POST两种请求的函数。
<!DOCTYPE html>   
<html>  
<body>  
  <p align="center" style="font-size: 30px;">  
    <button onclick="sendGetCorsRequest()">Send Get Request</button>  
    <button onclick="sendPostCorsRequest()">Send Post Request</button>  
  </p>  

  <script type="text/javascript">  
    var url = "http://bos-demo.bj.bcebos.com/bos.txt";  

    function sendGetCorsRequest() {  
      fetch(url).then(function(res) {  
        if (res.ok) {  
          alert("The response is ok, get request success!"); 
        } else {  
          alert("The response wasn't ok, got status ", res.status);  
        }  
      }, function(e) {  
        alert("Get request failed!", e);  
      });  
    }  

  function sendPostCorsRequest() {  
    fetch(url, { method: "POST" }).then(function(res) {  
        if (res.ok) {  
          alert("The response is ok, post request success!");  
        } else {  
          alert("The response wasn't ok, got status ", res.status);  
        }  
      }, function(e) {  
        alert("Post request failed!", e);  
      });  
    }  
  </script>  
</body>  
</html>

打开html文件之后(以chrome为例), 点击“Send Get Request”或者“Send Post Request”按钮,会发现两个请求都发送失败。

通过看chrome Console中报错提示我们看到,是因为没有找到Access-Control-Allow-Origin这个头。显然,这就是因为服务器没有配置CORS。

再进入Header界面,可见浏览器发送了带Origin的Request,因此是一个跨域请求,在Chrome上因为是打开的本地文件所以Origin是null。

配置Bucket CORS规则

CORS设置是由一条一条规则组成的,真正匹配的时候会从第一条开始逐条匹配,以最早匹配上的规则为准。现在添加第一条规则,使用最宽松的配置:

image.png

上图的配置代表着所有的Origin都允许访问,所有的请求类型都允许访问,所有的Header都允许,最大的缓存时间为10s,具体参数含义可以参考BOS产品配置文档。配置完成之后重新测试,结果如下:

Get请求结果:

Post请求结果:

可以发现,当我们配置了CORS规则之后,POST和GET请求都可以成功发送了。
除了最宽松的配置之外,还可以配置更精细的控制机制来实现针对性的控制。比如对某个bucket只允许get请求,不允许其它请求,或者只允许某个域名访问该bucket等,这些都可以在控制台进行配置。对于大部分场景来说,用户最好根据自己的使用场景来使用最小的配置以保证安全性。

注意事项

CORS配置项有以下注意事项:

  • Origins:配置的时候要带上完整的域信息,不要遗漏协议名如http,如果端口号不是默认的还要带上端口号。如果不确定的话,可以打开浏览器的调试功能查看Origin头。这一项支持使用通配符*,但是只支持一个,可以根据实际需要灵活配置。
  • Methods:按照需求开通对应的方法即可。
  • Headers:允许通过的Header列表,此建议没有特殊需求的情况下设置为*,大小写不敏感。
  • ExposeHeader:暴露给浏览器的Header列表,不允许使用通配符。具体的配置需要根据应用的需求来选择,只暴露需要使用的Header,如ETag等。如果不需要暴露这些信息,这里可以不填。如果有特殊需求可以单独指定,大小写不敏感。