跨域
浏览器安全
浏览器安全可以分为三大块--Web页面安全、浏览器网络安全和浏览器系统安全
本次主要介绍分析页面中的安全策略。在开始之前,我们先来做个假设,如果页面中没有安全策略的话,Web世界会是什么样的呢?
- Web 世界会是开放的,任何资源都可以接入其中,我们的网站可以加载并执行别人网站的脚本文件、图片、音频 / 视频等资源,甚至可以下载其他站点的可执行文件。
- Web 世界是开放的,这很符合 Web 理念。但如果 Web 世界是绝对自由的,那么页面行为将没有任何限制,这会造成无序或者混沌的局面,出现很多不可控的问题。
比如你打开了一个银行站点,然后又一不小心打开了一个恶意站点,如果没有安全措施,恶意站点就可以做很多事情:
- 修改银行站点的 DOM、CSSOM 等信息;
- 在银行站点内部插入 JavaScript 脚本;
- 劫持用户登录的用户名和密码;
- 读取银行站点的 Cookie、IndexDB 等数据;
- 甚至还可以将这些信息上传至自己的服务器,这样就可以在你不知情的情况下伪造一些转账请求等信息。
所以说,在没有安全保障的 Web 世界中,我们是没有隐私的,因此需要安全策略来保障我们的隐私和数据的安全。这也就是同源策略诞生的原因。
同源策略
Q: 什么是同源?
A: 如果两个 URL 的协议、域名和端口都相同,我们就称这两个 URL 同源。比如下面这两个 URL,它们具有相同的协议 HTTPS、相同的域名 time.geekbang.org,以及相同的端口 443,所以我们就说这两个 URL 是同源的。
https://time.geekbang.org/?category=1
https://time.geekbang.org/?category=0
Q: 什么是同源策略?
A: 浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同的源之间若想要相互访问资源或者操作 DOM,那么会有一套基础的安全策略的制约,我们把这称为同源策略。
具体来讲,同源策略主要表现在 DOM、Web 数据和网络这三个层面。
第一个,DOM 层面。同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。
{
let dom = opener.document
dom.body.style.display = "none"
}
第二个,数据层面。同源策略限制了不同源的站点读取当前站点的 Cookie、IndexDB、LocalStorage 等数据。由于同源策略,我们依然无法通过第二个页面的 opener 来访问第一个页面中的 Cookie、IndexDB 或者 LocalStorage 等内容。
opener.document.cookie
第三个,网络层面。同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。
var express = require("express");
var app = express();
app.use();
app.get("/hello", function (req, res) {
res.json(hello).end();
});
app.listen(3000, function () {
console.log("Example app listening on port 3000!");
});
function handleHi() {
axios.get("http://localhost:3000/hi").then((res) => {
console.log("====================================");
console.log(res);
console.log("====================================");
});
}
基于同源策略,采取的方式
为了使项目能够得以开发和使用,我们可以采用以下方法:
-
页面中可以嵌入第三方资源
弊端:极易引发XSS攻击以及CSRF攻击
解决方法:在浏览器中引入内容安全策略,称为 CSP。CSP 的核心思想是让服务器决定浏览器能够加载哪些资源,让服务器决定浏览器是否能够执行内联 JavaScript 代码。通过这些手段就可以大大减少 XSS 攻击。 -
引入跨域资源共享策略(CORS)
使用 XMLHttpRequest 和 Fetch 都是无法直接进行跨域请求的,因此浏览器又在这种严格策略的基础之上引入了跨域资源共享策略,让其可以安全地进行跨域操作 -
在浏览器中实现跨文档消息机制
可以通过 window.postMessage 的 JavaScript 接口来和不同源的 DOM 进行通信
跨域资源共享(CORS)
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求
简单请求
某些请求不会触发 CORS 预检请求。本文称这样的请求为“简单请求”,请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求满足所有下述条件,则该请求可视为“简单请求”:
-
使用下列方法之一:
GET
HEAD
POST -
Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。该集合为:
Accept
Accept-Language
Content-Language
Content-Type (需要注意额外的限制)
DPR
Downlink
Save-Data
Viewport-Width
Width -
Content-Type 的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded -
请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
-
请求中没有使用 ReadableStream 对象。
预检请求
与前述简单请求不同,“需预检的请求”要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求(其中包括:当前网页所在的域名是否在服务器的许可名单之中(Access-Control-Allow-Origin)以及可以使用哪些HTTP动词和头信息字段(Access-Control-Allow-Methods、Access-Control-Allow-Headers))。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
-
使用了下面任一 HTTP 方法:
PUT
DELETE
CONNECT
OPTIONS
TRACE
PATCH -
人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
Accept
Accept-Language
Content-Language
Content-Type (需要注意额外的限制)
DPR
Downlink
Save-Data
Viewport-Width
Width -
Content-Type 的值不属于下列之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain -
请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。
-
请求中使用了ReadableStream对象。
预检请求完成之后,发送实际请求
注:CORS 最初要求该行为,不过在后续的修订中废弃了这一要求。
HTTP 响应首部字段
1、Access-Control-Allow-Origin
Access-Control-Allow-Origin: <origin> | *
其中,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。
2、Access-Control-Expose-Headers
Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单,因为在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
3、Access-Control-Max-Age
Access-Control-Max-Age 头指定了preflight请求的结果能够被缓存多久
Access-Control-Max-Age: <delta-seconds>
4、Access-Control-Allow-Credentials
Access-Control-Allow-Credentials 响应头表示是否可以将对请求的响应暴露给页面。返回true则可以,其他值均不可以。
Credentials可以是 cookies, authorization headers 或 TLS client certificates。
Access-Control-Allow-Credentials: true
虽可以用在对preflight预检测请求的响应中,但由于预检请求被废弃了,所以暂不讨论
5、Access-Control-Allow-Methods
Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。
Access-Control-Allow-Methods: <method>[, <method>]*
6、Access-Control-Allow-Headers
Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。
Access-Control-Allow-Headers: <field-name>[, <field-name>]*
HTTP 请求首部字段
1、Origin
Origin 首部字段表明预检请求或实际请求的源站。
Origin: <origin>
2、Access-Control-Request-Method
Access-Control-Request-Method 首部字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。
Access-Control-Request-Method: <method>
3、Access-Control-Request-Headers
Access-Control-Request-Headers 首部字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。
Access-Control-Request-Headers: <field-name>[, <field-name>]*
JOSNP
利用了使用src引用静态资源时不受跨域限制的机制。主要在客户端搞一个回调做一些参数接收与操作的处理,并把这个回调函数告知服务器,而服务器端需要做的是按照JavaScript的语法把数据放到约定好的回调函数之中即可
它只支持get以及简单请求,兼容性好,相对来说不是很安全,因为它不是跨域规范,callback参数注入和资源访问授权设置未做限制。
参考:浏览器工作原理与实践-同源策略
HTTP访问控制(CORS)
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Server-Side_Access_Control