了解 CORS-跨站资源共享

缘起
  • 关于跨域问题,听得多,但一直未曾梳理。
  • 最近因为跨站 POST 无法上传图片,初识 cors,折腾多次;因认识不深而存在非认证即可上传安全漏洞;
  • 7月11-12日关于 ajax 处理 502 错误页面跳转问题(bug#692)又折腾了一次;
  • 7月19日又发现 IE8&9 存在兼容问题,因为 IE 是通过 XDomainRequest object 来支持 cors 的,幸好 MoonScript 帮助实现了在 ie8&9 下和其他浏览器一样进行跨站访问;但是不幸的是,不能携带 cookies,也就是说和认证有关的都不行;Eric Law 的文章 《XDomainRequest – Restrictions, Limitations and Workarounds》 揭示了这一点;
  • NoteCode 首先应用 cors,让我们解脱了 JSONP,同时在前后端代码上理清了很多问题。
什么是 CORS

假设有两个站点,A 站是一个应用站点,S 站是一个服务站点(比如 API 站点);A 站的 js(ajax) 访问 S 站提供的服务或者资源,这样的请求就是 cors 请求(Cross-Origin Resource Sharing 跨站资源共享)。
请移步 HTTP access control (CORS) @ mozilla.org Use Cases and Design Decision FAQ @ w3 wiki 查看详细。

Origin 请求头是 cors 请求的一个简单标识

简单地说,cors 请求的一个明显标识就是在请求中含有 Origin: 请求头。
Origin: 请求头和 Access-Control-Allow-Origin: 响应头组成了 cors 的一个最简单用法;

  • 实现了 CORS 协议的浏览器会自动发送 Origin: 请求头;
  • 实现了 CORS 协议的服务端如果允许 Origin: 指定的站点访问,则在回应请求头 Access-Control-Allow-Origin: 中包含这个站点即可;
预请求(Preflight request)和 实际请求(Actual request)

这是两类请求,一般简单请求通常并不需要事先搞一个 Preflight 请求,只有使用了特殊的 Content-Type、有非标准的或者自定义的 request header 时,浏览器才会在提起实际请求前首先发送 Preflight 请求。

何时 preflight?
满足以上所列条件的跨站点请求就是简单请求,不需要事先 preflight
  • preflight 以 OPTIONS 方法发起请求
    目的在于发起实际业务请求之前,向服务端事先了解对跨站点访问控制策略的实施情况;
  • preflight 请求头
    - Origin: 应用站点,类似:http://www.example.com
    - Access-Control-Request-Method: 列出实际请求希望使用的HTTP方法;
    - Access-Control-Request-Headers:
    Access-Control-Request-Headers: 列出自定义的 header;如果 Content-Type 值为自定义的,也需要将 Content-Type 列入;
  • preflight 请求头示例
Origin: http://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
  • preflight 响应头示例
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400 指明对于 OPTIONS 请求的缓存时长;
实际请求(Actual Request)
  • 请求头
Origin: http://www.example.com
Cookie: ...

Requests with credentials 时(XMLHttpRequest.withCredentials = true;),在请求头中会携带上 Cookie: 请求头;
这对于提供服务的 S 站来说,是一个重要且简捷的地方,S 站可以管理自己域下的 Cookie,就可以避免在主域名下种 cookie 而导致的种种问题。
应用站点和服务站点是同一个主域名时,这是一个简单做法。
比如:WWW 应用站点收集用户登录信息(简单如用户名/密码),通过 AJAX 到 API 服务站点进行认证后访问服务站点的资源,后续所有 API 操作都需要带上 Cookie 等凭证信息

  • 响应头
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Credentials: true
  • 错误提示


    假如在 Preflight 时,`Access-Control-Allow-Headers` 没有包含实际请求所提交的相关请求头,则会报告错误提示
dropzone.js 的预请求问题
dropzone 是一个支持拖拽的文件上传 js 库,比如图片上传
同源策略(Same Origin Policy

同源策略是客户端脚本(javascript)的重要安全度量标准。所有浏览器都遵循这个标准。例如:XMLHttpRequest 请求就只能访问源站的资源,也就是说 A 站的 js 只能访问 A 站的资源;如果访问其他网站资源,浏览器会拒绝将返回的内容传递给 js,目的在于避免 A 站的恶意脚本窃取 B 站页面的敏感信息;

关于跨站请求的可能的误解

浏览器并不会阻止 AJAX 发起跨站请求,浏览器也会正常接收服务端的返回内容,只不过为了履行安全策略,浏览器并不会向上层调用者返回 AJAX 响应内容,也就是说返回结果被浏览器拦截了;
我们在处理 bug# 692 时就误以为 jQuery 有问题,Google 了半天,在 stackoverflow 上才有一个提示说可能是 CORS 问题;

使用 curl 调试 cors 请求(OPTIONS 请求示例)
curl -I --verbose -X OPTIONS \
-H "Origin: http://www.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: X-Requested-With" \
http://api.example.com/index.php?r=j
服务端 PHP 代码示例
if(isset($_SERVER['HTTP_ORIGIN']) && stripos($_SERVER['HTTP_ORIGIN'], APP_TOP_DOMAIN) !== false){
    header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
    header('Access-Control-Allow-Credentials: true');

    if($_SERVER['REQUEST_METHOD']=='OPTIONS'){
        header('Access-Control-Allow-Headers: Content-Type, Cache-Control, X-Requested-With');
        header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
        header('Access-Control-Max-Age: 86400');
    }
}
header('Access-Control-Expose-Headers: Date');
关于 cors 的缓存
Last-Modified:Wed, 17 Aug 2016 16:00:00 GMT
Cache-Control:public, max-age=86400
  • Vary: Origin 问题


    未标识 `Vary: Origin` 导致的问题
fonts 需要授权使用

fonts 要么放到同一个域名下,要么放到 cdn,但 需要 enabling CORS
nginx 片段示例:add_header Access-Control-Allow-Origin *

参考
bug# 692 备注
  • 前端 js 无法直接捕获 API 50X 错误;
    服务端接收到了 jQuery AJAX 请求,也正确地返回了,但是 jQuery 就是没有正确返回调用,本来以为这是 jQuery 问题,其实不是;
  • 前端 js 只好通过超时来推导服务端出现问题;
  • 超时发生时,直接跳到 502 页面;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容