浏览器有一个基本的安全策略叫做“同源策略”。所谓同源就是必须是同一个web服务器提供的服务,即同样的host和port。而且必须是同样的协议。对于一个URI来说,//前边的就是协议,即http://www.rethink.fun 和https://www.rethink.fun 也不是同源的。
同源策略是为了保证用户的安全,它包含以下几个限制:
1. Cookie,LocalStroage 和 IndexDB 非同源不能访问。因为我们经常保留一些重要的信息,比如用户的session信息在Cookie里,如果恶意网站可以访问你银行网站的cookie,从而伪造你的session信息,那么肯定是不安全的。
2. iframe里加载的页面如果和主页面不同源,那么彼此就不能获取对方的DOM元素。
3. AJAX请求。AJAX只能请求同源的web服务,如果你的web服务需要让给别的网站提供服务怎么办?那就需要引入今天的CORS了。
CORS是Cross-origin resource sharing的缩写。它就是用来解决跨域AJAX请求的。
CORS的实现是Web Client和Web Server共同实现的。
对于前端开发来说,不需要做额外的工作,非同源请求和同源请求是一样的。浏览器会帮我们做一些事情:
浏览器如果发现AJAX的请求是非同源的,就会自动添加一些头信息,有时还会多请求一次进行确认,后面我们会说到。单这些对于开发者和最终的用户都是透明的。所以CORS的支持主要工作是在后端开发。

这里我们把请求分为两种,普通请求和特殊请求

普通请求

普通请求的请求方法只能是 HEAD,GET,POST中的一个。而且HTTP的头信息不能超过一下几个
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
对于普通请求,浏览器直接发出请求,和同源请求相比,在头信息中额外添加一个字段:* Origin*
比如下边这样


GET /testapi HTTP/1.1
Origin: http://www.rethink.fun
Host: http://api.baidu.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上边的例子是在www.rethink.fun 的页面里发起了对http://api.baidu.com/testapi 的跨域资源请求。其中Origin: http://www.rethink.fun 是浏览器帮我们自动添加的。服务器需要根据这个Origin来决定是否同意这个请求。
如果服务器端同意这个Origin指定的域名的跨源请求,服务器返回的响应里会添加几个头信息字段:


Access-Control-Allow-Origin: http://www.rethink.fun
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: myVar1,myVar2
Content-Type: text/html; charset=utf-8

我们分别来看一下返回的几个头信息:
1. Access-Control-Allow-Origin 这个字段是必须的,除了返回请求时指定的origin,也可以是一个 * 来表明可以接受任意域名的请求。
2. Access-Control-Allow-Credentials 该字段可选,是布尔值,如果服务器在客户发起非同源请求是需要客户端的Cookie就需要给这个值设置为true,如果不需要就不要包含该头信息。如果Access-Control-Allow-Credentials设置为true,则Access-Control-Allow-Origin就不能为*了,就必须指定为发起请求的域名,而且也只能发送服务器域名下的cookie
3. Access-Control-Expose-Headers 该字段可选,在Client拿回跨源请求时,默认只能从ResponseHeader里获取6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma 如果想要添加额外的字段,就需要在这个头信息里指定。多个值的话需要用逗号分隔。

如果服务器返回的头信息里不包含必须的Access-Control-Allow-Origin字段,那么浏览器就会报错。

特殊请求

不是普通请求的都是特殊请求了,比如请求方法是PUT或者DELETE,更为常见的是Content-Type是applicaiton/json。Content-Type是客户端发送给服务器的数据类型,dataType是客户端期望服务器返回的数据类型。
特殊CORS请求时,client会在正式的post/put/delete请求前增加一次OPTIONS请求。叫做preflight。
在preflight的OPTIONS请求时,客户端会在request的header里增加以下信息:
1.Origin,必须项,表明自己的域名,服务器需要判断是否接受该域名请求。
2.Access-Control-Request-Method,必须项,表明自己请求的HTTP Method,PUT/DELETE/POST,服务器据此来判断是否支持
3.Access-Control-Request-Headers,可选项,告诉服务器自己额外发送的头信息。

下边是一个例子:


OPTIONS /testapi HTTP/1.1
Origin: http://www.rethink.fun
Access-Control-Request-Method: POST
Access-Control-Request-Headers: myVar1,myVar2
Host: http://api.baidu.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

当服务器接收到这个跨源访问的preflight的OPTIONS请求后,会对Origin,Access-Control-Request-Method以及Access-Control-Request-Headers进行规则验证。每种web服务器配置的规则不一样。
如果验证成功了,服务器返回的response头里会添加以下几个头信息:


Access-Control-Allow-Origin: http://www.rethink.fun
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: myVar1,myVar2
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 864000

其中最重要的是Access-Control-Allow-Origin,如果没有这个字段,Client就认为检验失败,同样Client也会判断自己将要发出的真正请求的HTTP Method是否在Access-Control-Allow-Methods里。Access-Control-Max-Age表示需要重新预检的时间,单位是秒。

当Client通过预检后,特殊CORS请求的过程就和普通的CORS请求一样的。每个请求都会包含Origin 字段,服务器返回的头信息里也会包含一个Access-Control-Allow-Origin头信息。

需要注意的是如果你测试一定要在不同域的网页来发起请求,如果同域,Client不会加上Origin字段,服务器也不会返回Access-Control-Allow-Origin。
在PlayFramework2.6里的配置如下:


play.filters.enabled += "play.filters.cors.CORSFilter"
play.filters.cors {
  pathPrefixes = ["/v1/test"]
  allowedHttpMethods = ["GET", "POST"]
  allowedHttpHeaders = ["Accept"]
  preflightMaxAge = 3 days
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注

%d 博主赞过: