今天故事的主角还是大家熟识的二狗子。二狗子拿到了一笔项目奖金,在好好犒劳了自己一顿后,决定把剩下的钱在银行存个定期。
他用浏览器访问了 www.bank.com,输入了用户名和密码后,成功登录。
bank.com 返回了 cookie 用来标识二狗子这个用户。

不得不说,浏览器是个认真负责的工具,它会把这个 cookie 记录下来,以后二狗子每次向 bank.com 发起 HTTP 请求,浏览器都会准确无误地把 cookie 加入到 HTTP 请求头部中,一起发送到 bank.com,这样 bank.com 就知道二狗子已经登陆过了,就可以按照二狗子的请求来做事情,比如查看余额、转账取钱。
二狗子存完钱,看着账户余额,心中暗喜。于是,他打开了 www.meinv.com,去看自己喜欢的电影。
但二狗子不知道的是,浏览器把 meinv.com 的 HTML、JavaScript 都下载到本地,开始执行。而其中某个 JavaScript 中,偷偷创建了一个 XMLHttpRequest 对象,然后向 bank.com 发起了 HTTP 请求 。

浏览器严格按照规定,把之前存储的 cookie 也添加到 HTTP 请求中。但是 bank.com 根本不知道这个 HTTP 请求是 meinv.com 的 JavaScript 发出的,还以为是二狗子发出的。bank.com 检查了cookie,发现这是一个登录过的用户,于是兢兢业业地去执行请求命令,二狗子的个人信息就泄露了。(ps. 实际中实施这样一次攻击不会这么简单,银行网站肯定是做了其他很多安全校验的措施,本故事只是用来说明基本原理。)
可怜的二狗子还不知道发生了什么,已经遭受了钱财损失。那我们来帮他复盘一下为什么会发生这种情况。
首先,每当访问 bank.com 的时候,不管是人点击按钮访问链接,还是通过程序的方式,存储在浏览器的 bank.com 的 cookie 都会进行传递。
其次,从 meinv.com 下载的 JavaScript 利用 XMLHttp 访问了 bank.com。
第一点我们是无法阻止的,如果阻止了,cookie 就丧失了它的主要作用。
对于第二点,浏览器必须做出限制,不能让来自 meinv.com 的 JavaScript 去访问 bank.com。这个限制就是同源策略。
同源策略
协议(http/https)相同; 域名(domain)相同; 端口(port)相同。
https://www.bank.com/save.html (✅) http://www.bank.com/withdraw.html (❌,协议不同) https://bank.com/login.html (❌,域名不同) https://www.bank.com:8080/withdraw.html (❌,端口不同)
try {
fetch('https://opentalk.upyun.com/data')
} catch (err) {
console.error(err);
}Access to fetch at 'https://opentalk.upyun.com/data' from origin 'https://www.upyun.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled
什么是 CORS
简单请求
Accept Accept-Language Content-Language Content-Type 仅限以下三种: text/plain multipart/form-data application/x-www-form-urlencoded
GET /data HTTP/2 Host: opentalk.upyun.com accept-encoding: deflate, gzip accept: */* origin: https://www.upyun.com ......
Access-Control-Allow-Origin: https://www.upyun.com Access-Control-Allow-Headers: Authorization Access-Control-Expose-Headers: X-Date Access-Control-Allow-Credentials: true
Cache-Control Content-Language Content-Type Expires Last-Modified Pragma
非简单请求
fetch('http://opentalk.upyun.com/data/', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CUSTOM-HEADER': '123'
}
})OPTIONS /data/ HTTP/1.1 Host: opentalk.upyun.com Origin: http://www.upyun.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-MY-CUSTOM-HEADER, Content-Type
HTTP/1.1 200 OK Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH Access-Control-Max-Age: 86400 Access-Control-Allow-Headers: X-Date, range, X-Custom-Header, Content-Type Access-Control-Expose-Headers: X-Date, X-File, Content-type ......
Access-Control-Allow-Methods,这是个不可缺少的字段,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。 Access-Control-Allow-Headers 字段为一个逗号分隔的字符串,表明服务器支持的所有请求头部信息字段,不限于浏览器在预检中请求的字段。 Access-Control-Max-Age:该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是 1 天(86400 秒),在此期间,不用再发出另一条预检请求。
又拍云 CORS 配置

