Fluge Site

同源策略

这套安全策略由Netscape提出,并延续至今。它规定:JavaScript脚本只能访问与其同一来源的资源(现在很多资源是通过ajax发起异步请求来获取的,如果没有跨域这个是禁止的)。
所谓同源是指,域名,协议,端口相同。不同源的客户端脚本(javascript、ActionScript)在没明确授权的情况下,不能读写对方的资源。严格隔离不相关的网站提供的内容,防止客户端数据机密性或完整性丢失。
假设你已经成功登录Gmail服务器,同时在同一个浏览器访问恶意站点(另一个浏览器选项卡)。没有同源策略,攻击者可以通过JavaScript获取你的用户信息,你的邮件以及其他敏感信息,比如说阅读你的私密邮件,发送虚假邮件,看你的聊天记录等等。假如把这个换成银行账户,那就很恐怖了。
可以说同源策略是现如今浏览器安全的基石。但是如果不能突破同源策略,把所有的资源放在同一服务器下,现在看来是不现实,必须有一中方式去平衡这种安全和便捷的机制–跨域。现在一般的跨域使用的是CORS(基本所有浏览器支持)和JSONP(一些比较的老的应用使用)。

CROS跨域

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
简单说一下我对CROS的理解。就相当于我想跟一个邻居借东西(浏览器向服务器发送跨域请求)。我首先要去敲门,然后就是几种情况,一种是邻居家里你敲门没有反应(服务器端没有设置跨域),你跟不知道邻居家里的具体情况,借东西肯定是失败的。一种是邻居进行了应答,但是告诉你我跟你不熟,不借东西给你。另外一种就是邻居进行了应答,并借给你东西。

要了解CROS跨域,首先需要知道浏览器在ajax发起跨域请求的时候做了什么事。
每次浏览器检查到这个请求是跨域请求时,会在请求的头部添加一些附加的头信息,根据请求的不同,有时会多发起一次请求但是这些用户是感觉不到,前端的ajax调用也没有发什么变化,改变的是服务器端的回应。所以cros跨域由于浏览器的支持,现在只要在服务器端,也就是后端进行配置就可以了。

cros分类

浏览器将cros的跨域请求分为两种:一种是简单请求,一种的非简单请求,两种的区别就是在发起非简单请求的时候,浏览器会在请求的前面先发起一次预请求,看请求的后端是否允许当前这个域名和这个方法进行访问。

1
2
3
4
5
6
7
8
9
10
11
简单请求(不满足以下条件的都是非简单请求):
(1)请求方法是以下三种方法之一:
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded 或 multipart/form-data 或text/plain

cros的简单请求

对于简单请求的cros跨域请求,浏览器会在就是在头信息之中,增加一个Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)服务器根据这个值,决定是否同意这次请求。
如果Origin的字段在服务器端允许的范围内,服务器成功响应,会在返回的头信息中多几个字段

1
2
3
4
5
6
//该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
Access-Control-Allow-Origin: http://api.bob.com
//该字段可选。它的值是一个布尔值,表示是否允许发送Cookie
Access-Control-Allow-Credentials: true
//该字段可选。
Access-Control-Expose-Headers: FooBar

如果Origin指定的域名不在许可范围内。服务器也会正常的返回HTTP的回应(状态吗有可能是200),只是头信息里面不会增加上面的字段。浏览器收到回应后 会检测是否有Access-Control-Allow-Origin字段,如果没有回抛出一个错误,被XMLHttpRequest的onerror回调函数捕获

非简单请求

非简单请求就是不满足简单请求条件的都是非简单请求,值得注意的是Content-Type: application/josn都是非简单请求。
浏览器如果检测到发起的请求是非简单请求,会在正式发起HTTP请求前,浏览器会自动发起一次HTTP查询请求,称为”预检”请求(preflight)。
预请求的方法是OPTIONS,这个方法和GET,POST等方法是一样的。这个方法表示请求是用来询问的服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
一个预检请求
OPTIONS /cors HTTP/1.1
//表示请求来自哪个源。
Origin: http://api.bob.com
//该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,这个是PUT。
Access-Control-Request-Method: PUT
//该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

预检请求允许,服务器端的回应
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
//表示http://api.bob.com可以请求数据。该字段也可以设为*,表示同意任意跨源请求。
Access-Control-Allow-Origin: http://api.bob.com
//该字段必需,表明服务器支持的所有跨域请求的方法。
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain;

预检请求被否定时,跟简单请求一样会回复一个正常的HTTP请求,然后浏览器检查,被被XMLHttpRequest对象的onerror回调函数捕获就会报错
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

beego的跨域

在工作中用beego框架遇到的跨域问题的解决

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
beego.InsertFilter("*", beego.BeforeRouter,cors.Allow(&cors.Options{
//允许的访问的域名
AllowOrigins: []string{"*"},
//允许的访问的方法
AllowMethods: []string{"OPTIONS","GET","PUT","DELETE","POST"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))
beego.Run()
}

JSONP的简介

CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。


参考:
浏览器的同源策略及突破方法
跨域资源共享 CORS 详解