浏览器工作原理&前端安全

1,069 阅读21分钟

网络安全

三原则

  1. 在传输中,不允许明文传输用户隐私数据;
  2. 在本地,不允许明文保存用户隐私数据;
  3. 在服务器,不允许明文保存用户隐私数据;

http是明文传输,WiFi、路由器、运营商、机房等多个物理设备节点,如果在这中间任意一个节点被监听,传输的内容就会完全暴露,,这一攻击手法叫做MITM(Man In The Middle)中间人攻击。 在网络来说,我们知道不论 POST 请求和 GET 请求都会被抓包,在没有使用 HTTPS 的情况下,抓包我们是防不住的,如果明文传输用户隐私,那后果就不说了。

很多用户密码是通用的,一旦被不法分子窃取,去其他网站撞库,造成损失。 上文说到http传输因为有三大风险

  • 窃听风险(eavesdropping):第三方可以获知通信内容。
  • 篡改风险(tampering):第三方可以修改通信内容。
  • 冒充风险(pretending):第三方可以冒充他人身份参与通信。

所以提到了https https 可以认为是 http + TLS TLS 是传输层加密协议,它的前身是 SSL 协议,如果没有特别说明,SSL 和 TLS 说的都是同一个协议。

加密传输(避免明文传输)

1. 对称加密

加解密使用同一个密钥 客户端和服务端进行通信,采用对称加密,如果只使用一个秘钥,很容易破解;如果每次用不同的秘钥,海量秘钥的管理和传输成本又会比较高。

2.非对称加密

需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥) 非对称加密的模式则是:

  • 乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的

  • 甲方获取乙方的公钥,然后用它对信息加密

  • 乙方得到加密后的信息,用私钥解密。

    但当服务端要返回数据,如果用公钥加密,那么客户端并没有私钥用来解密,而如果用私钥加密,客户端虽然有公钥可以解密,但这个公钥之前就在互联网上传输过,很有可能已经有人拿到,并不安全,所以这一过程只用非对称加密是不能满足的。 (严格来讲,私钥并不能用来加密,只能用作签名使用,这是由于密码学中生成公钥私钥时对不同变量的数学要求是不同的,因此公钥私钥抵抗攻击的能力也不同) 所以为了满足即使非对称

image.png

https

HTTPS 的出发点是解决HTTP明文传输时信息被篡改和监听的问题。

  • 为了兼顾性能和安全性,使用了非对称加密+对称加密的方案。

  • 为了保证公钥传输中不被篡改,又使用了非对称加密的数字签名功能,借助CA机构和系统根证书的机制保证了HTTPS证书的公信力。

    只传递证书、明文信息、加签加密后的明文信息,注意不传递CA公钥(防止中间人攻击),客户端浏览器可以通过系统根证书拿到CA公钥。(系统或浏览器中内置的CA机构的证书和公钥成为了至关重要的环节)

加密存储 千万不要用明文存储密码 如果用明文存储密码(不管是存在数据库还是日志中),一旦数据泄露,所有用户的密码就毫无保留地暴露在黑客的面前,开头提到的风险就可能发生,那我们费半天劲加密传输密码也失去了意义。

总结 如果我们想要尽可能保证用户的信息安全,我们需要做以下的工作

  • 使用https请求
  • 利用RSA加密密码并传输数据
  • 用BCrypt或者PBKDF2单向加密,并存储

强制使用HTTPS

一些网站购买了SSL证书并将其配置到Web服务器上,以为这就算完事儿了。但这只是表明你启用了HTTPS选项,而用户很可能不会注意到。为确保每个用户都从HTTPS中受益,你应该将所有传入的HTTP请求重定向至HTTPS。这意味着任何一个访问你的网站的用户都将自动切换到HTTPS,从那以后他们的信息传输就安全了。

配合cookie的secure参数,禁止cookie在最初的http请求中被带出去(中间人拦截)。

TCP三次握手四次挥手

Tcp是传输控制协议(Transmission Control Protocol)是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议

第一次握手:请求连接client->SYN=1, 随机seq=x(数据包首字节序列号) 第二次握手:同意应答,SYN和ACK都置为1,ack=x+1,随机seq=y,返回确认连接 第三次握手:client检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1;——>Server,Server检查ack是否为y+1,ACK是否为1,正确则连接成功!

认证授权+浏览器存储

什么是认证(Authentication)

验证当前用户的身份,证明“你是你自己” 互联网中的认证:

  • 用户名密码登录
  • 邮箱发送登录链接
  • 手机号接收验证码
什么是授权(Authorization)

用户授予第三方应用访问该用户某些资源的权限 安装手机应用时(是否允许访问相册、地理位置等权限) 登录微信小程序(是否允许获取昵称、头像、地区、性别等个人信息)

  • 实现授权的方式有:cookie、session、token、OAuth
什么是凭证(Credentials)

实现认证和授权的前提是需要一种媒介(证书) 来标记访问者的身份 登录成功后,服务器给用户使用的浏览器颁发一个令牌,表明身份,每次请求时带上。

什么是 Cookie
  • HTTP 是无状态的协议,每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。
  • cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
  • cookie 是不可跨域的:每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain

特点: Cookie 的大小受限,一般为 4 KB; 同一个域名下存放 Cookie 的个数是有限制的,不同浏览器的个数不一样,一般为 20 个; Cookie 支持设置过期时间,当过期时自动销毁;(max-age单位秒,如果是负数,为临时cookie关闭浏览器失效;默认是-1) 每次发起同域下的 HTTP 请求时,都会携带当前域名下的 Cookie; 支持设置为 HttpOnly,防止 Cookie 被客户端的 JavaScript 访问

什么是 Session
  • session 是另一种记录服务器和客户端会话状态的机制
  • session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中 SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。
什么是localStorage

特点

  • 大小限制为 5MB ~10MB;
  • 在同源的所有标签页和窗口之间共享数据;
  • 数据仅保存在客户端,不与服务器进行通信;
  • 数据持久存在且不会过期,重启浏览器后仍然存在;
  • 对数据的操作是同步的。
什么是sessionStorage
  • sessionStorage 的数据只存在于当前浏览器的标签页;
  • 数据在页面刷新后依然存在,但在关闭浏览器标签页之后数据就会被清除;
  • 与 localStorage 拥有统一的 API 接口;
  • 对数据的操作是同步的。
什么是 Token(令牌)
  • 访问资源接口(API)时所需要的资源凭证
  • 简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串) 特点:
  • 服务端无状态化、可扩展性好
  • 支持移动端设备
  • 安全
  • 支持跨程序调用
什么是 JWT
  • JSON Web Token(简称 JWT)是目前最流行的跨域认证解决方案。(不使用cookie)

方式:通过Authorization;通过url;跨域的时候,可以把 JWT 放在 POST 请求的数据体里

和session、token的区别是JWT已经包含用户信息,所以不用再去数据库里查询了,而且

什么是 XSS

Cross-Site Scripting(跨站脚本攻击),是一种代码注入攻击

  • 存储性(任何可输入存入数据库的地方,注入脚本,服务端渲染时将脚本拼接html中返回给浏览器)
  • 反射性(脚本写入url,如路由传参,诱导用户点击,服务端渲染时将脚本拼接html中返回给浏览器)
  • DOM性(脚本写入url,前端 JavaScript 取出 URL 中的恶意代码并执行) 防范:cookie设置readOnly禁止js脚本访问cookie 前端服务端对输入框设置格式检查 转义 HTML(存储、反射) 改成纯前端渲染(存储、反射) 使用react就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患用.textContent、.setAttribute()。

什么是 CSRF 跨站请求伪造(英语:Cross-site request forgery) 用户已经登录了安全网站A,诱导用户访问网站B,B利用A获取的凭证去访问A,绕过用户验证

  • 1.登录受信任网站A,并在本地生成Cookie。
  • 2.在不登出A的情况下,访问危险网站B。

防范:同源策略(origin referrer) token samesite

Base64编码由来

因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就不能通过邮件传送。Base64就是一种基于64个可打印字符来表示二进制数据的表示方法。

ASCII码 在计算机中,所有的数据在存储和运算时都要使用二进制数表示,像a、b、c、d这样的52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示,用来统一规定上述常用符号用哪些二进制数来表示

unicode、utf-8、ASCII、base64、哈希md5 ASCII美国信息互换标准代码,用一个字节存储128个字符(其中包括33个控制字符(具有某些特殊功能但是无法显示的字符) 产生原因: 在计算机中,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),例如,像a、b、c、d这样的52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII编码,统一规定了上述常用符号用哪些二进制数来表示 [2]  。

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。.Base64编码是从二进制到字符的过程

浏览器工作原理

异步编程

与同步相对的异步则可以理解为在异步操作完成后所要做的任务,它们通常以回调函数或者 Promise 的形式被放入事件队列,再由事件循环 ( Event Loop ) 机制在每次轮询时检查异步操作是否完成,若完成则按事件队列里面的执行规则来依次执行相应的任务。

javascript的运行机制(单线程、任务队列、EventLoop、微任务、宏任务)

单线程特点

单线程可以避免多线程操作带来的复杂的同步问题。

任务队列(JavaScript的运行机制)
  1. 所有同步任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
Event Loop

每次 Tick 会查看任务队列中是否有需要执行的任务。每次 Tick 的过程就是查看是否有待处理事件,如果有则取出相关事件及回调函数放入执行栈中由主线程执行。待处理的事件会存储在一个任务队列中 1. onclick 由浏览器内核的 DOM Binding 模块来处理,当事件触发的时候,回调函数会立即添加到任务队列中。 2. setTimeout 会由浏览器内核的 timer 模块来进行延时处理,当时间到达的时候,才会将回调函数添加到任务队列中。 3. ajax 则会由浏览器内核的 network 模块来处理,在网络请求完成返回之后,才将回调添加到任务队列中。

javascript是单线程的,浏览器是多线程的。 进程和线程都是操作系统的概念,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,即进程是操作系统进行资源分配和独立运行的最小单元。

进程(process)

进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,即进程是操作系统进行资源分配和独立运行的最小单元。 当我们启动一个应用,计算机会至少创建一个进程,cpu会为进程分配一部分内存,应用的所有状态都会保存在这块内存中,应用也许还会创建多个线程来辅助工作,这些线程可以共享这部分内存中的数据。如果应用关闭,进程会被终结,操作系统会释放相关内存。

线程(thread)

  • 进程内部的一个执行单元,是被系统独立调度和分派的基本单位。系统创建好进程后,实际上就启动执行了该进程的主执行线程
  • 进程就像是一个有边界的生产厂间,而线程就像是厂间内的一个个员工,可以自己做自己的事情,也可以相互配合做同一件事情,所以一个进程可以创建多个线程。
  • 线程自己不需要系统重新分配资源,它与同属一个进程的其它线程共享当前进程所拥有的全部资源。 PS: 进程之间是不共享资源和地址空间的,所以不会存在太多的安全问题,而由于多个线程共享着相同的地址空间和资源,所以会存在线程之间有可能会恶意修改或者获取非授权数据等复杂的安全问题。


Chrome 采用多进程架构

主要进程

  • Browser Process 浏览器的主进程(负责协调、主控) (1)负责包括地址栏,书签栏,前进后退按钮等部分的工作 (2)负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问 (3)负责各个页面的管理,创建和销毁其他进程
  • Renderer Process 负责一个 tab 内关于网页呈现的所有事情,页面渲染,脚本执行,事件处理等

image.png

  • Plugin Process 负责控制一个网页用到的所有插件,如 flash 每种类型的插件对应一个进程,仅当使用该插件时才创建
  • GPU Process 负责处理 GPU 相关的任务,3D 绘制等

优点 由于默认 新开 一个 tab 页面 新建 一个进程,所以单个 tab 页面崩溃不会影响到整个浏览器。 同样,第三方插件崩溃也不会影响到整个浏览器。 多进程可以充分利用现代 CPU 多核的优势。

缺点 系统为浏览器新开的进程分配内存、CPU 等资源,所以内存和 CPU 的资源消耗也会更大。 不过 Chrome 在内存释放方面做的不错,基本内存都是能很快释放掉给其他程序运行的。

一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件触发线程。

1.JavaScript引擎是基于事件驱动单线程执行的,JavaScript引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JavaScript线程在运行JavaScript程序。

2.GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行。但需要注意,GUI渲染线程与JavaScript引擎是互斥的,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JavaScript引擎空闲时立即被执行。

3.事件触发线程,当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JavaScript引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeout、也可来自浏览器内核的其他线程如鼠标点击、Ajax异步请求等,但由于JavaScript的单线程关系所有这些事件都得排队等待JavaScript引擎处理(当线程中没有执行任何同步代码的前提下才会执行异步代码)

问题

  1. 为什么 Javascript 要是单线程的 ?

JavaScript 为处理页面中用户的交互,以及操作 DOM 树、CSS 样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。 如果 JavaScript 是多线程的方式来操作这些 UI DOM,则可能出现 UI 操作的冲突。但为了避免因为引入了锁而带来更大的复杂性,Javascript 在最初就选择了单线程执行。

  1. 为什么 JS 阻塞页面加载 ?

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。所以为了防止渲染的不可预期结果,浏览器设置 GUI 渲染线程与 JavaScript 引擎为互斥的关系。

  1. css 加载会造成阻塞吗 ?

CSS 加载不会阻塞 DOM 的解析(并行), Render Tree 是依赖于 DOM Tree 和 CSSOM Tree 的所以CSS 加载会阻塞 Dom 的渲染,同时css 会阻塞后面 js 的执行

  1. 什么是 CRP,即关键渲染路径(Critical Rendering Path)? 如何优化 ?

image.png

Html可以逐步解析,和css解析是并行的,但是css不行,因为css的每个属性都是可以改变cssom的,比如后面的把前面设置的font-size覆盖等,所以必须等cssom构建完毕才能进入下一个阶段。CSS的加载速度与构建CSSOM的速度将直接影响首屏渲染速度,因此在默认情况下CSS被视为阻塞渲染的资源。

通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个script标签时,DOM构建将暂停,直至脚本完成执行。但由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS。

优化围绕三因素

关键资源数量(js、css)

关键路径长度

关键字节的数量(字节越小、下载和处理速度都会更快——压缩)

具体做法:

优化dom

html文件尽可能小,删除冗余代码,压缩代码,使用缓存(http cache)

优化cssom

仅把首屏需要的css通过style标签内嵌到head里,其余的使用异步方式非阻塞加载(如Critical CSS)

避免使用@import

@import会把css引入从并行变成串行加载

异步js

所有文本资源都应该尽可能小,删除未使用的代码、缩小文件的尺寸(Minify)、使用gzip压缩(Compress)、使用缓存(HTTP Cache)

可以为script添加async属性异步加载

5.从输入url浏览器渲染的流程。

解析 HTML 文件,构建 DOM 树,同时浏览器主进程负责下载 CSS 文件 CSS 文件下载完成,解析 CSS 文件成树形的数据结构,然后结合 DOM 树合并成 RenderObject 树 布局 RenderObject 树 (Layout/reflow),负责 RenderObject 树中的元素的尺寸,位置等计算 绘制 RenderObject 树 (paint),绘制页面的像素信息 浏览器主进程将默认的图层和复合图层交给 GPU 进程,GPU 进程再将各个图层合成(composite),最后显示出页面

6.Event Loop至少包含两个队列,macrotask队列和microtask队列

async/await成对出现,async标记的函数会返回一个Promise对象,可以使用then方法添加回调函数。await后面的语句会同步执行。但 await 下面的语句会被当成微任务添加到当前任务队列的末尾异步执行。

先微后宏

回流 (Reflow)

当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。 会导致回流的操作:

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如::hover)
  • 查询某些属性或调用某些方法

重绘 (Repaint)

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

回流比重绘的代价要更高。 有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。

  1. 多线程的优点和缺点分别是什么? 优点:

1、将耗时较长的操作(网络请求、图片下载、音频下载、数据库访问等)放在子线程中执行,可以防止主线程的卡死;

2、可以发挥多核处理的优势,提升cpu的使用率。

缺点:

1、每开辟一个子线程就消耗一定的资源;

2、会造成代码的可读性变差;

3、如果出现多个线程同时访问一个资源,会出现资源争夺的情况。