同源策略与CORS

同源策略

  • 它是浏览器最核心也是最基本的安全功能,浏览器的同源策略,限制了来自不同的 “document” 或脚本对当前的 “document” 的读取或设置某些属性
  • 如果没有同源策略,可能 a.com 的一段 Javascript 脚本在 b.com 未曾加载此脚本时,也可以随意涂改 b.com 的页面,为了不发生页面行为混乱,浏览器提出 ”Origin” 这一概念,来自不同 Origin 的对象互不干扰。
  • 影响 Origin(源) 的因素有:host(域名或IP)、子域名、端口、协议:
URL OutCome Reason
http://store.company.com/dir2/other.html Success
http://store.company.com/dir/inner/anthoer.html Success
https://store.company.com/secure.html Failure Different protocol
http://store.company.com:81/dir/etc.html Failure Different port
http://news.company.com/dir/other.html Failure Different host
  • 在浏览器中 <script><img><iframe><link> 等标签都可以跨域加载资源,而不受到同源策略的限制。这些带 “src” 属性的标签每次加载时,实际上是由浏览器发起了一次GET请求。不同于 XMLHttpRequest 的是,通过 src 属性加载的资源,浏览器限制了 JavaScript 的权限,使其不能读、写返回内容
  • XMLHttpRequest 可以访问同源资源,一开始不能跨域访问,但随着互联网发展,W3C制定了 XMLHttpRequest 跨域访问的标准(CORS),它需要通过目标与返回的HTTP头来授权是否可以跨域访问,因为HTTP头对于 JavaScript 来说一般无法控制
  • 但是浏览器的同源策略并非坚不可摧,如 IE8 的 Css 跨域漏洞:
www.a.com/test.html:
    <body>
    {}body{font-family:
    aaaaaaaaaaaaaaa
    bbbbbbbbbbbbbbbbb
    </body>
www.b.com/test2.html:
    <style>
    @import url("http://www.a.com/test.html")
    </style>
    <script>
        setTimeout(function(){
            var t = document.body.currentStyle.fontFamily;
            alert(t);
        },2000);
    </script>
  • 上述代码中 www.b.com/test2.html 中通过 @import 加载了 www.a.com/test.html 为 Css 渲染进入当前页面 DOM,同时通过 document.body.currentStyle.fontFamily 访问此内容,问题在 IE 的 CSS Parse 过程中,IE 将 fontFamily 后面的内容当做 value,而通过 <script> 等标签仅能加载资源,不能读写资源内容,而这个漏洞能够跨域读取页面内容,绕过了同源策略

CORS

  • CORS 是 W3C 制定的“跨域资源共享”的标准,它允许浏览器向跨源服务器发出 XMLHttpRequest 请求,克服了 AJAX 只能同源的使用限制
  • CORS 需要浏览器和服务器同时支持,目前所有浏览器都支持该功能,IE 浏览器不能低于 IE10,整个通信过程是浏览器自动完成的,不需要用户参与,CORS 与 AJAX 代码上没有差别,浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息
  • 因此实现 CORS 的通信关键是服务器,只要服务器实现了 CORS 接口,就可以跨源通信

两种请求

  • 浏览器将 CORS 请求分为两类:简单请求(Simple Request)和非简单请求(not-so-simple reuqest)
  • 只要满足以下两种条件就属于简单请求,否则属于非简单请求
请求方法是以下三种方法之一:
    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 请求,就是在头信息中添加一个 Origin 字段
  • 下面是浏览器在一个请求中添加 Origin 字段的例子
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
  • 上面头信息中,Origin 字段用来说明本次请求来自哪个源(协议+域名+端口),服务器根据这个值,决定是否同意本次请求
  • 如果 Origin 指定的源不在许可范围内,服务器会返回一个正常的 HTTP 协议,但头信息没有包含 Access-Control-Allow-Origin 字段,从而抛出一个错误,被 XMLHttpRequest的onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是200
  • 如果 Origin 指定的域名在许可范围内,服务器的响应会多出几个头信息
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时 Origin 字段的值,要么是一个*,表示接受任意域名的请求
  • Access-Control-Allow-Credentials:该字段可选,它是一个布尔值,表示是否允许发送 Cookie
  • Access-Control-Expose-Headers:该字段可选,CORS 请求时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到 6 个基本字段:ache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果想拿到其它字段就必须在 Access-Control-Expose-Headers 里面指定,上面例子指定了 getResponseHeader('FooBar') 可以返回 FooBar 字段的值

withCredentials 属性

  • CORS 请求默认不发送 Cookie 和 HTTP 认证信息,如果要把 Cookie 发送到服务器,一方面要服务器同意
Access-Control-Allow-Credentials = true
  • 另一方面开发者必须在 AJAX 请求中打开 withCredentials 属性
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
  • 有些浏览器会默认一起发送 Cookie,这时可以显式关闭 withCredentials
xhr.withCredentials = false;
  • 如果要发送 Cookie,Access-Control-Allow-Origin 就不能为星号,必须指定明确的、与请求网页一致的域名,并且 Cookie 依然遵循同源策略

非简单请求

  • 非简单请求是那种对服务器有特殊需求的请求,比如 PUT 或 DELETE 方法,或者 Content-Type 字段的类型是 application/json
  • 非简单请求的 CORS 请求会在正式通信之前增加一次 HTTP 请求查询,称为“预检”请求
  • 浏览器会先询问服务器,当前网页是否在服务器的许可名单中,以及可以使用那些 HTTP 动词和头字段信息,只有得到肯定大幅,浏览器才会发送 XMLHttpRequest 请求,否则报错
  • 下面是一段浏览器的 JS 脚本
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
  • 上面代码 HTTP 请求方法是 PUT,并且发送一个自定义的头信息 X-Custom-Header
  • 浏览器发现,这是一个非简单请求,就自动发出一个“预检”请求,要求服务器确认,下面是一个“预检”请求的例子
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
  • “预检”请求的方法是 OPTIONS,表示这个请求是用来询问的,头信息里关键字段是 Origin,表示来自哪个源
  • 除了 Origin 字段,还包括两个特殊字段
    • Access-Control-Request-Method:必须,用来列出 CORS 请求用的 HTTP 方法,上例是 PUT
    • Access-Control-Request-Headers:该字段是一个逗号分离的字符串,指定额外发送的头字段信息,上例是 X-Custom-Header

预检请求的回应

  • 服务器收到“预检”请求后检查 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 字段后确认允许跨源请求,就可以作出回应
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
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
  • 上面例子中 Access-Control-Allow-Origin 字段表示 http://api.bob.com 可以请求数据,该字段也可以为星号,表示同意任意跨源请求
Access-Control-Allow-Origin: *
  • 如果浏览器否定了“预检”请求会返回一个正常的 HTTP 回应,但不包含 CORS 相关头信息,因此会触发一个错误,被 XMLHttpRequest 对象的 onerror 回调函数捕获
  • 服务器回应的其它 CORS 相关字段如下:
Access-Control-Allow-Methods: GET, POST, PUT  #表示支持的跨域请求方法
Access-Control-Allow-Headers: X-Custom-Header  #表示服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true  #该字段与简单请求时相同
Access-Control-Max-Age: 1728000  #指定本次预检请求的有效期,单位为秒,即在缓存该条回应期间,不用在发出另一条预检请求
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容