一张图带你看懂浏览器缓存机制
流程解读
浏览器发送请求之后,会执行浏览器缓存机制,跑来判断是否有缓存。浏览器缓存机制,又分为强缓存和协商缓存。
浏览器发送请求后,首先会判断强缓存是否新鲜,而强缓存主要依靠的是 cache-control 或者 expire 来进行判断是否命中缓存。 如果强缓存新鲜那么浏览器使用缓存。如果强缓存不新鲜,走的是协商缓存。 浏览器这个时候会在请求头上带上 If-None-Match 或者是 If-Modified-SInce,分别返回的是 ETag 或者 Last-Modified,浏览器再去通过 ETag 或者 Last-Modified 去判断是否命中协商缓存,如果命中,那么返回304,获取缓存,否则发送请求200之后会返回资源。
cache-control 和 expire
总的来说 cache-control 的优先级要比 expire 高,因为 cache-control 跟准确些
cache-control
no-store: 客户端不可缓存
no-cache: 强制走协商缓存
public || private: 是否能被中间人缓存(cdn、中间人代理),默认为 private
max-age: 保存缓存信息的最大时间(秒)
expire
存储的是过期日期的本地时间,一旦本地时间被改了,该时间就失效了
ETag 和 Last-Modified
ETag: 主要存的是资源的哈希值,可以保证每份资源是唯一性。
Last-Modified:上一次修改资源的 GMT 时间,粒度为秒级。
ETag 比 Last-Modified 优先级高的原因如下:
- ETag 主要依赖的是对资源本身进行哈希,而依靠修改时间。如果说资源的内容没有发生改变,那么这个 ETag 就不会发生改变。当发送请求的时候 ETag 会查看哈希值是否进行改变,也就是说,如果时间变化了,但是实际资源的内容并没有发生改变,那么 ETag也不会发生改变,这样的话就不用重新去 get 请求。
- ETag 的粒度是秒级以下,Last-Modified 粒度为秒级,所以 如果1秒钟之内修改了多次文件的话 Last-Modified 并不会检测出来,因此在这方面来说 ETag 会比 Last-Modified 更加准确。
- 一些服务器不能精确得到文件的最后修改时间。
浏览器安全及防范
XSS 攻击
def: 跨站脚本攻击,攻击者通过向用户网站嵌入脚本,执行恶意脚本,从而达到攻击目的
攻击形式具有以下三种:
- 反射XSS(reflected xss): 注入恶意链接,诱导攻击者点击
- 存储型 XSS(stored xss):攻击代码存储到服务器中,每当访问服务器的时候,会触发这类代码,可以盗取不同用户 cookie
- DOM 型 XSS:通过更改 DOM 树实现
攻击方法则不外乎是通过执行 script 脚本或者把恶意脚本通过可输入组件或者从 URL 带上 script 实现脚本注入
// 输入框
我是个恶意脚本<script> window.alert('我是病毒')</script>
// 更改 DOM
加入image <img scr="http:xxxx" />
防御措施
- 把提交的字符 < 、>、 % 、””、’’、+、/ 进行转义,可以把 script 标签这些给转换掉
- cookie 设置 http-only(不能通过 JS 来访问)
- 严格的 CSP (content-secure-policy) 进行限制,在 meta 处进行设置,参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
- 对于输入框,设置合理长度、字符类型等
CSRF 攻击
def: 跨站伪造请求,用户不知情下,利用用户权限(cookie),伪造用户发请求
实例:小明在银行网站A 转账,黑客从 A 获取到小明发送的 cookie。此时黑客可以拿着小明的 cookie 向 A 的后台发送请求,伪造小明的请求给黑客账户转账
防御措施
既然是通过伪造用户请求来进行攻击,那么只需要确认用户本人操作即可
- 短信验证码
- token 验证
- 同源检测:Origin 和 Referer 验证。浏览器限制对 referer 改动,所以发送请求的网站都会在 referer 看到,所以服务器可以对其判断是否响应该请求
- Same-site 设置
SQL 注入
Def: 主要是用户通过发送请求 SQL 参数恶意注入,通过在 server 端拼接 SQL 对数据库进行增删查改。
防御措施
- 分级管理,控制用户权限,对系统管理员才有对数据库增删查改的权利
- 正则表达式拦截请求,将 SQL 语句进行过滤
- 隐藏数据库错误信息,可以用户通过返回错误信息进行数据库字段信息判断
DDOS
Def: 短时间内,僵尸设备大量向服务器发送请求,使得服务器不能响应全部请求,导致请求堆积,消耗大量带宽,出现雪崩效应
这里的原理主要是通过 tcp 对收到 server 响应不予以回应,导致 server 不断发送报文进行确认,这个时候因为没有完成连接,所以不能够释放连接,同时又到达了最大连接数,这个时候会出现崩溃。
防御措施
这个时候不外乎是过滤和抗量
- 流量管制:负载均衡、API 网关、CDN
- 快速自动扩容
- 非核心服务降级
有一种情况是静态资源被劫持了,比如 CDN 静态资源被篡改,可以利用 SRI 来防范
中间人攻击
http 是明文传输,也就是说
- 中间人可以通过抓取 http 报文,查看传输内容
- 信息被篡改不可知
- 对方的身份无法验证
防御措施
- 利用 https 对报文进行加密(TLS + HTTP)
Https 有以下特性:
可靠性:加密
不可抵赖性:数字签名
当然假若算法不够健壮时候,数字签名可能被暴力破解。所以说成也证书,败也证书。
同源策略
在没有浏览器安全保障中,当打开一个网站后,不小心再打开恶意网站,那么会出现恶意网站,可能出现网站中的cookie被盗取、被插入JS脚本等情况,也就是CSRF 和XSS 攻击。这个时候就需要浏览器的安全机制,同源策略来进行限制网站的行为。
原理
浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。相同源主要包括:协议、主机、端口号一致。
主要有以下三个方面的限制行为:
- DOM 层面:读写操作限制,第二个页面能通过 window.opener 获取前一个页面的dom
- 数据层面:Cookie、IndexDB、LocalStorage 等数据限制。
- 网络层面:限制通过XHR等方式发送到不同源的站点
跨域
上面说了同源策略保证了浏览器的安全性,那么不同源间的数据就无法进行共享了。特别是前后端分离的项目时,也因为浏览器同源策略的限制,导致我们在不同源之间通信,出现了浏览器接收不到服务端返回数据的问题。安全性是做到了,但又失了便利性。实在需要不同源进行跨域共享也是可以的,有以下方法:
跨域资源共享 CORS
使用 XMLHttpRequest 和 Fetch 都是无法直接进行跨域请求,所以出现了cors。
如果是简单请求(GET、POST、HEAD),只需要服务端设置 Access-Control-Allow-Origin (告诉浏览器允许向服务端请求资源的域名),前端无需设置。如果需要带上cookie请求,双方都需要设置。
后端修改请求头:
header(‘Access-Control-Allow-Origin:*’);允许访问的网址
header(‘Access-Control-Allow-Method:POST,GET’);允许访问的方式
非简单请求就需要先进行一次预检请求发送OPTION请求,服务器收到预检请求后,检查这些特殊的method和 header 自己能否接受,再发送真实请求。主要是因为非简单请求有可能是需要修改服务端资源。
⚠️ 需要带 cookie 或者 http 认证信息发送身份凭证的。一般浏览器不会发送这些信息凭证。如果需要发送,客户端需要加上
withCredentials
为true。服务端需要携带Access-Control-Allow-Credentials``: true
(告知浏览器是否可以将对响应给前端JS代码),否则浏览器不会把响应内容给客户端。并且服务器响应的 method, header, origin 都不能为通配符,否则请求失败。
Nginx 反向代理
原理:同源策略是浏览器的,但是不是http协议的,所以服务器调用http借口,只使用http协议,不会执行JS脚本,不需要同源策略
通过nginx配置一个代理服务器,通过代理服务器作为跳板,访问服务器。对于客户端而言不需要做任何配置,对于服务端而言,只是把消息发送到另一台服务器。隐藏了真实服务器的IP地址。
(图出处:知乎@小晶)
跨文档消息机制 postMessage
优点:
- 不需要后端介入就可以做到跨域,一个函数外加俩个参数(请求url,发送数据)就可以搞定;
- 移动端兼容性好;
缺点: - 无法做到一对一的传递方式:监听中需要做很多消息的识别,由于postMessage发出的消息对于同一个页面的不同功能相当于一个广播的过程,该页面的所有onmessage都会收到,所以需要做消息的判断;
- 同时三方可以通过截获,注入html或者脚本的形式监听到消息,从而能够做到篡改的效果,所以在postMessage和onMessage中一定要做好这方面的限制;
用法:
// 发送请求
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.demo2.com');
// 处理返回的数据
window.addEventListener('message', function(e) {
alert('data from demo2 ---> ' + e.data);
}, false);
// ----------------------------
//接受请求
window.addEventListener('message', function(e) {
alert('data from demo1 ---> ' + e.data)
}
// 返回数据
window.postMessage(JSON.stringify(data), 'http://www.demo1.com');
WebSocket
浏览器与服务器全双工通信,同时允许跨域通讯,是服务端推送的一种很好的实现。
cookie 和 session
http 是无状态的,因此无法确定用户身份。Cookie 和 session 服务于会话跟踪,能够通过这两个来确认身份。
cookie 原理
cookie 存储用户信息在客户端,每次发送请求会带上cookie, 服务端则通过 cookie 信息来判断,是否用户本人操作
session 原理
session 是通过 session 来判断是否当前用户操作,每次发送请求的时候,可以在请求URL进行拼接,或者是传送信息的时候,加个用来放session的字段传送,服务端通过客户端传送的 session 与服务端保存的 session 来判断是否用户本人操作。
会话跟踪 cookie/session 最佳实践
单纯用cookie,用户信息会保存在客户端。如果出现中间人攻击,能够获取用户信息,或者用户将自己的cookie信息修改,能够伪造成他人访问。
单纯用session的话需要跟server商量session放在哪个地方为妥
涉及两个的最佳实践是:session和cookie相结合,session ID放到cookie里面。鉴权信息为session ID,传送载体是cookie。浏览器自动把同源的cookie放到请求头里面。同时保证用户信息信息安全性
cookie 和 session 的区别有:
- 保存位置:cookie: 客户端。session: 服务端。
- 安全:cookie:容易出现 csrf 攻击并且在客户端能被修改(http-only能防范);session:服务端无法直接修改伪造
- 作用域:cookie:同源策略下发送,session:在服务端可以跨域共享(这里涉及分布式 session)
- 关闭浏览器后保持时间:cookie:默认关闭即没有;session:默认保持30分钟
- 类型: cookie: string; session: object
- 大小:cookie:一般不超过 4k; session: 可以灵活控制
cookie 属性
每个上网的人都离不开cookie,cookie用来跟踪会话提高用户体验,但是在安全性方面需要注意,设置cookie自身的属性能够避免一些麻烦,接下来介绍一下部分重要的cookie属性。
信息相关:
- Name-Value:cookie的名字和其属性,呈 k-v结构
- Domain: cookie作用域。如果后端未指定 Domain,那么默认情况下,域的值就与 document.domain 或者 location.hostname。如果服务端设置的 cookie 不包含在当前的 document.domain 中,那么会被浏览器拒绝。比如a.test.com 可以设置为 test.com,但不能设置为 Baidu.com 或者 b.test.com。
⚠️:该域不要与同源策略搞混,cookie只区分域,不区分端口和协议。只要域相同,cookie可以共享
- Expire/Max-age:cookie 有效期。默认会话期间有效,也就是当浏览器关闭的时候就会被移除。但是目前浏览器很多支持会话恢复的功能,所以在一定期限内重新打开浏览器也会被恢复。expire 一般是具体的GMT过期时间,max-age 是设置多少秒过期。一般如果同时存在,那么 max-age 比 expire 优先级高。
安全相关:
- Http-only: 服务端可以设置,cookie不能被 JS 获取或修改。但是实在要改,在浏览器的application中也能手动改。
- Secure:只有当使用 HTTPS 协议的时候才会被发送,如果使用 http ,客户端会收不到
- Same-site:限制第三方cookie,是防止CSRF攻击中的其中一种,限制发送cookie至第三方网站。其属性有 strict、lax、none。
Strict:完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。
Lax:默认值。除了下面三种情况外,不发送第三方 Cookie- 链接:
<a href="..."></a>
- 预加载请求:
<link rel="prerender" href="..."/>
- GET 表单:
<form method="GET" action="...">
None : 任何情况都发送,一定要把Secure属性带上。禁止明文传输
- 链接:
浏览器存储
storage:其出现的原因主要有人们希望有一种在cookie之外存储回话数据的途径以及望有一种存储大量可以跨会话存在的数据的机制。
localStorage:
- 有API易于操作
- 更安全,同源才能共享,onStorage能实现同源窗口通信
- 存储数据量更大,存储大约5M左右
- 保存在硬盘里,存储永久
sessionStorage:
- 保存在内存中,与local Storage不同,没有过期时间设置,浏览器关掉则会移除
- 存储当前会话页数据
- 即使不同源,从一个session派生出来的页面同样能访问之前设置的数据
indexDB:
出现主要是因为storage存储量有限,需要大量结构化的数据存储时就不符合需求。
提供索引进行高性能查找。一般用于保存大量用户数据和有搜索需要场景,网络断开时能够做离线操作。