🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] ## 什么是 XSS 攻击? ### 概念 * XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。 * XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。 ### 攻击者可以通过这种攻击方式可以进行以下操作: * 获取页面的数据,如DOM、cookie、localStorage; * DOS攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器; * 破坏页面结构; * 流量劫持(将链接指向某网站); #### (2)攻击类型 XSS 可以分为存储型、反射型和 DOM 型: * 存储型指的是恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。 * 反射型指的是攻击者诱导用户访问一个带有恶意代码的 URL 后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有 XSS 代码的数据后当做脚本执行,最终完成 XSS 攻击。  * DOM 型指的通过修改页面的 DOM 节点形成的 XSS。 ### 存储型XSS的攻击步骤: 1. 攻击者将恶意代码提交到⽬标⽹站的数据库中。 2. ⽤户打开⽬标⽹站时,⽹站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。 3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。 4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。 这种攻击常⻅于带有⽤户保存数据的⽹站功能,如论坛发帖、商品评论、⽤户私信等。 ### 反射型XSS的攻击步骤: 1. 攻击者构造出特殊的 URL,其中包含恶意代码。 2. ⽤户打开带有恶意代码的 URL 时,⽹站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。 3. ⽤户浏览器接收到响应后解析执⾏,混在其中的恶意代码也被执⾏。 4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。 * **反射型 XSS 跟存储型 XSS 的区别**是:存储型 XSS 的恶意代码存在数据库⾥,反射型 XSS 的恶意代码存在 URL ⾥。 * 反射型 XSS 漏洞常⻅于通过 URL 传递参数的功能,如⽹站搜索、跳转等。 由于需要⽤户主动打开恶意的 URL 才能⽣效,攻击者往往会结合多种⼿段诱导⽤户点击。 ### DOM型XSS的攻击步骤: 1. 攻击者构造出特殊的 URL,其中包含恶意代码。 2. ⽤户打开带有恶意代码的 URL。 3. ⽤户浏览器接收到响应后解析执⾏,前端 JavaScript 取出 URL 中的恶意代码并执⾏。 4. 恶意代码窃取⽤户数据并发送到攻击者的⽹站,或者冒充⽤户的⾏为,调⽤⽬标⽹站接⼝执⾏攻击者指定的操作。 * **DOM 型 XSS 跟前两种 XSS 的区别**:DOM 型 XSS 攻击中,取出和执⾏恶意代码由浏览器端完成,属于前端JavaScript ⾃身的安全漏洞,⽽其他两种 XSS 都属于服务端的安全漏洞。 ### ****如何防御 XSS 攻击?**** * 可以从浏览器的执行来进行预防,一种是使用纯前端的方式,不用服务器端拼接后返回(不使用服务端渲染)。另一种是对需要插入到 HTML 中的代码做好充分的转义。 * 对于 DOM 型的攻击,主要是前端脚本的不可靠而造成的,对于数据获取渲染和字符串拼接的时候应该对可能出现的恶意代码情况进行判断。 * 使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击。 * CSP 指的是内容安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截由浏览器自己来实现。 * 通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式 。 * 对一些敏感信息进行保护,比如 cookie 使用 http-only,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户执行一些操作。 ## 什么是 CSRF 攻击? ### 概念 * CSRF 攻击指的是**跨站请求伪造攻击**,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。 * CSRF 攻击的**本质是****利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。** ### 攻击类型 * 常见的 CSRF 攻击有三种: 1. GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交。 2. POST 类型的 CSRF 攻击,比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。 3. 链接类型的 CSRF 攻击,比如在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。 ### 如何防御 CSRF 攻击? **CSRF 攻击可以使用以下方法来防护:** * **进行同源检测**,服务器根据 http 请求头中 origin 或者 referer 信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当 origin 或者 referer 信息都不存在的时候,直接阻止请求。这种方式的缺点是有些情况下 referer 可以被伪造,同时还会把搜索引擎的链接也给屏蔽了。所以一般网站会允许搜索引擎的页面请求,但是相应的页面请求这种请求方式也可能被攻击者给利用。(Referer 字段会告诉服务器该网页是从哪个页面链接过来的) * **使用 CSRF Token 进行验证**,服务器向用户返回一个随机数 Token ,当网站再次发起请求时,在请求参数中加入服务器端返回的 token ,然后服务器对这个 token 进行验证。这种方法解决了使用 cookie 单一验证方式时,可能会被冒用的问题,但是这种方法存在一个缺点就是,我们需要给网站中的所有请求都添加上这个 token,操作比较繁琐。还有一个问题是一般不会只有一台网站服务器,如果请求经过负载平衡转移到了其他的服务器,但是这个服务器的 session 中没有保留这个 token 的话,就没有办法验证了。这种情况可以通过改变 token 的构建方式来解决。 * **对** **Cookie 进行****双重验证**,服务器在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串,然后当用户再次向服务器发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数中,然后服务器通过对 cookie 中的数据和参数中的数据进行比较,来进行验证。使用这种方式是利用了攻击者只能利用 cookie,但是不能访问获取 cookie 的特点。并且这种方法比 CSRF Token 的方法更加方便,并且不涉及到分布式访问的问题。这种方法的缺点是如果网站存在 XSS 漏洞的,那么这种方式会失效。同时这种方式不能做到子域名的隔离。 * **在设置 cookie 属性的时候设置 Samesite ,限制 cookie 不能作为被第三方使用**,从而可以避免被攻击者利用。Samesite 一共有两种模式,一种是严格模式,在严格模式下 cookie 在任何情况下都不可能作为第三方 Cookie 使用,在宽松模式下,cookie 可以被请求是 GET 请求,且会发生页面跳转的请求所使用。 ### 其他如:网络劫持有哪几种,如何防范? ⽹络劫持分为两种: 1. **DNS****劫持**: (输⼊京东被强制跳转到淘宝这就属于dns劫持) * DNS强制解析: 通过修改运营商的本地DNS记录,来引导⽤户流量到缓存服务器 * 302跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是可以进⾏劫持处理的,再对劫持的内存发起302跳转的回复,引导⽤户获取内容 2. **HTTP****劫持**: (访问⾕歌但是⼀直有贪玩蓝⽉的⼴告),由于http明⽂传输,运营商会修改你的http响应内容(即加⼴告) * DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。 ## 浏览器缓存 ### 对浏览器的缓存机制的理解 **浏览器缓存的全过程:** * 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用; * 下一次加载资源时,由于强制缓存优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期; * 如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求; * 服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回 304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200; * 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200; ![](https://img.kancloud.cn/e6/92/e6921c78e442167ef8bc45dc6d15df8d_995x657.png) 很多网站的资源后面都加了版本号,这样做的目的是:每次升级了 JS 或 CSS 文件后,为了防止浏览器进行缓存,强制改变版本号,客户端浏览器就会重新下载新的 JS 或 CSS 文件 ,以保证用户能够及时获得网站的最新更新。 ### 浏览器资源缓存的位置有哪些? * 资源缓存的位置一共有 3 种,按优先级从高到低分别是: 1. **Service Worker:**Service Worker 运行在 JavaScript 主线程之外,虽然由于脱离了浏览器窗体无法直接访问 DOM,但是它可以完成离线缓存、消息推送、网络代理等功能。它可以让我们**自由控制**缓存哪些文件、如何匹配缓存、如何读取缓存,并且**缓存是持续性的**。当 Service Worker 没有命中缓存的时候,需要去调用 `fetch` 函数获取 数据。也就是说,如果没有在 Service Worker 命中缓存,会根据缓存查找优先级去查找数据。**但是不管是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示是从 Service Worker 中获取的内容。** 2. **Memory Cache:** Memory Cache 就是内存缓存,它的效率最快,**但是内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。** 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。 3. **Disk Cache:** Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache **胜在容量和存储时效性上。** 在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。**并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。** **Disk Cache:** Push Cache 是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。**并且缓存时间也很短暂,只在会话(Session)中存在,一旦会话结束就被释放。** 其具有以下特点: * 所有的资源都能被推送,但是 Edge 和 Safari 浏览器兼容性不怎么好 * 可以推送 `no-cache` 和 `no-store` 的资源 * 一旦连接被关闭,Push Cache 就被释放 * 多个页面可以使用相同的 HTTP/2 连接,也就是说能使用同样的缓存 * Push Cache 中的缓存只能被使用一次 * 浏览器可以拒绝接受已经存在的资源推送 * 可以给其他域名推送资源 ### 协商缓存和强缓存的区别 **强缓存** * 使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。 * 强缓存策略可以通过两种方式来设置,分别是 http 头信息中的 Expires 属性和 Cache-Control 属性。 1. 服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。 2. Expires 是 http1.0 中的方式,因为它的一些缺点,在 HTTP 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,它提供了对资源的缓存的更精确的控制。它有很多不同的值,`Cache-Control`可设置的字段: * `public`:设置了该字段值的资源表示可以被任何对象(包括:发送请求的客户端、代理服务器等等)缓存。这个字段值不常用,一般还是使用max-age=来精确控制; * `private`:设置了该字段值的资源只能被用户浏览器缓存,不允许任何代理服务器缓存。在实际开发当中,对于一些含有用户信息的HTML,通常都要设置这个字段值,避免代理服务器(CDN)缓存; * `no-cache`:设置了该字段需要先和服务端确认返回的资源是否发生了变化,如果资源未发生变化,则直接使用缓存好的资源; * `no-store`:设置了该字段表示禁止任何缓存,每次都会向服务端发起新的请求,拉取最新的资源; * `max-age=`:设置缓存的最大有效期,单位为秒; * `s-maxage=`:优先级高于max-age=,仅适用于共享缓存(CDN),优先级高于max-age或者Expires头; * `max-stale[=]`:设置了该字段表明客户端愿意接收已经过期的资源,但是不能超过给定的时间限制。 * 一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires。 **no-cache和no-store很容易混淆:** * no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存; * no-store 是指不使用任何缓存,每次请求都直接从服务器获取资源。 **协商缓存** * 如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。 * 上面已经说到了,命中协商缓存的条件有两个: * `max-age=xxx` 过期了 * 值为`no-store` * 使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。 * 协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 **Etag** 和 **Last-Modified** 属性。 1. 服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,这样会造成缓存命中的不准确。 2. 因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last-Modified 的方式更加精确。 * 当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好不要设置 Etag 属性。 **总结:** * 强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。 ## 浏览器渲染原理 ### 浏览器的渲染过程 浏览器渲染主要有以下步骤: * 首先解析收到的文档,根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。 * 然后对 CSS 进行解析,生成 CSSOM 规则树。 * 根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和 DOM 元素相对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。 * 当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。 * 布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用 UI 基础组件。 大致过程如图所示: ![](https://img.kancloud.cn/d9/fa/d9fa287cc87b4d78930078b19a5b0905_663x307.png) **注意:** 这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。 ### 浏览器渲染优化 * **针对JavaScript:** JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变,来进行优化: 1. 尽量将JavaScript文件放在body的最后 2. body中间尽量不要写`<script>`标签 3. `<script>`标签的引入资源方式有三种,有一种就是我们常用的直接引入,还有两种就是使用 async 属性和 defer 属性来异步引入,两者都是去异步加载外部的JS文件,不会阻塞DOM的解析(尽量使用异步加载)。 4. 三者的区别如下: * **script** 立即停止页面渲染去加载资源文件,当资源加载完毕后立即执行js代码,js代码执行完毕后继续渲染页面; * **async** 是在下载完成之后,立即异步加载,加载好后立即执行,多个带async属性的标签,不能保证加载的顺序; * **defer** 是在下载完成之后,立即异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果DOM树已经准备好,则立即执行。多个带defer属性的标签,按照顺序执行。 * **针对CSS:**使用CSS有三种方式:使用**link、@import、内联样式**,其中link和@import都是导入外部样式。它们之间的区别: * **link**:浏览器会派发一个新等线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码 * **@import**:GUI渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前不会继续渲染(阻碍浏览器渲染) * **style**:GUI直接渲染 * 外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。所以CSS一般写在headr中,让浏览器尽快发送请求去获取css样式。 * 所以,在开发过程中,导入外部样式使用link,而不用@import。如果css少,尽可能采用内嵌样式,直接写在style标签中。 * **针对DOM树、CSSOM树:** 可以通过以下几种方式来减少渲染的时间: * HTML文件的代码层级尽量不要太深 * 使用语义化的标签,来避免不标准语义化的特殊处理 * 减少CSSD代码的层级,因为选择器是从左向右进行解析的 * **减少回流与重绘:** * 操作DOM时,尽量在低层级的DOM节点进行操作 * 不要使用`table`布局, 一个小的改动可能会使整个`table`进行重新布局 * 使用CSS的表达式 * 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。 * 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素 * 避免频繁操作DOM,可以创建一个文档片段`documentFragment`,在它上面应用所有DOM操作,最后再把它添加到文档中 * 将元素先设置`display: none`,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 * 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于**浏览器的渲染队列机制**。 ### 渲染过程中遇到 JS 文件如何处理? * JavaScript 的加载、解析与执行会阻塞文档的解析,也就是说,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复继续解析文档。也就是说,如果想要首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性。 ### 什么是文档的预解析? * Webkit 和 Firefox 都做了这个优化,当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。需要注意的是,预解析并不改变 DOM 树,它将这个工作留给主解析过程,自己只解析外部资源的引用,比如外部脚本、样式表及图片。 ### CSS 如何阻塞文档解析? * 理论上,既然样式表不改变 DOM 树,也就没有必要停下文档的解析等待它们。然而,存在一个问题,JavaScript 脚本执行时可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题。所以如果浏览器尚未完成 CSSOM 的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟 JavaScript 脚本执行和文档的解析,直至其完成 CSSOM 的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建 CSSOM,然后再执行 JavaScript,最后再继续文档的解析。 ## 浏览器本地存储 ### 1浏览器本地存储 ### Cookie * Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie的大小只有4kb,它是一种纯文本文件,每次发起HTTP请求都会携带Cookie。 **Cookie的特性:** * Cookie一旦创建成功,名称就无法修改 * Cookie是无法跨域名的,也就是说a域名和b域名下的cookie是无法共享的,这也是由Cookie的隐私安全性决定的,这样就能够阻止非法获取其他网站的Cookie * 每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb * 有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,即使加密也于事无补,无需知道cookie的意义,只要转发cookie就能达到目的 * Cookie在请求一个新的页面的时候都会被发送过去 **如果需要域名之间跨域共享Cookie,有两种方法:** 1. 使用Nginx反向代理 2. 在一个站点登陆之后,往其他网站写Cookie。服务端的Session存储到一个节点,Cookie存储sessionId **Cookie有哪些字段,作用分别是什么** * **Name**:cookie的名称 * **Value**:cookie的值,对于认证cookie,value值包括web服务器所提供的访问令牌; * **Size**: cookie的大小 * **Path**:可以访问此cookie的页面路径。 比如domain是abc.com,path是`/test`,那么只有`/test`路径下的页面可以读取此cookie。 * **Secure**: 指定是否使用HTTPS安全协议发送Cookie。使用HTTPS安全协议,可以保护Cookie在浏览器和Web服务器间的传输过程中不被窃取和篡改。该方法也可用于Web站点的身份鉴别,即在HTTPS的连接建立阶段,浏览器会检查Web网站的SSL证书的有效性。但是基于兼容性的原因(比如有些网站使用自签署的证书)在检测到SSL证书无效时,浏览器并不会立即终止用户的连接请求,而是显示安全风险信息,用户仍可以选择继续访问该站点。 * **Domain**:可以访问该cookie的域名,Cookie 机制并未遵循严格的同源策略,允许一个子域可以设置或获取其父域的 Cookie。当需要实现单点登录方案时,Cookie 的上述特性非常有用,然而也增加了 Cookie受攻击的危险,比如攻击者可以借此发动会话定置攻击。因而,浏览器禁止在 Domain 属性中设置.org、.com 等通用顶级域名、以及在国家及地区顶级域下注册的二级域名,以减小攻击发生的范围。 * **HTTP**: 该字段包含`HTTPOnly` 属性 ,该属性用来设置cookie能否通过脚本来访问,默认为空,即可以通过脚本访问。在客户端是不能通过js代码去设置一个httpOnly类型的cookie的,这种类型的cookie只能通过服务端来设置。该属性用于防止客户端脚本通过`document.cookie`属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。但是,HTTPOnly的应用仍存在局限性,一些浏览器可以阻止客户端脚本对Cookie的读操作,但允许写操作;此外大多数浏览器仍允许通过XMLHTTP对象读取HTTP响应中的Set-Cookie头。 * **Expires/Max-size** : 此cookie的超时时间。若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。 **总结:** * 服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。一条cookie 包括了5个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的时间,domain 是域名、path是路径,domain 和 path 一起限制了 cookie 能够被哪些 url 访问。secure 规定了 cookie 只能在确保安全的情况下传输,HttpOnly 规定了这个 cookie 只能被服务器访问,不能使用 js 脚本访问。 ### LocalStorage * LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。 **LocalStorage的优点:** * 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息 * LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在 * 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带 **LocalStorage的缺点:** * 存在浏览器兼容问题,IE8以下版本的浏览器不支持 * 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage * LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问。 SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。 **SessionStorage****与LocalStorage对比:** * SessionStorage和LocalStorage都在**本地进行数据存储**; * SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage**只有在同一浏览器的同一窗口下才能够共享**; * LocalStorage和SessionStorage**都不能被爬虫爬取**。 ### . Cookie、LocalStorage、SessionStorage区别 浏览器端常用的存储技术是 cookie 、localStorage 和 sessionStorage。 * **cookie:**其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。 * **sessionStorage:**html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。 * **localStorage:**html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享。 ## 浏览器同源策略 * **什么是同源策略** * 跨域问题其实就是浏览器的同源策略造成的。 * 同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:**协议**、**端口号**、**域名**必须一致。 * **同源政策主要限制了三个方面:** * 当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB。 * 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。 * 当前域下 ajax 无法发送跨域请求。 * 同源政策的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。 ## 如何解决跨越问题 ### 1、**跨域资源共享(CORS)** * 下面是MDN对于CORS的定义: * 跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器  让运行在一个 origin (domain)上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP 请求。 * CORS需要浏览器和服务器同时支持,整个CORS过程都是浏览器完成的,无需用户参与。因此实现**CORS的关键就是服务器,只要服务器实现了CORS请求**,就可以跨源通信了。 * **浏览器将CORS分为简单请求和非简单请求:** * 简单请求不会触发CORS预检请求。若该请求满足以下两个条件,就可以看作是简单请求: * **请求方法是以下三种方法之一:** * HEAD * GET * POST * **HTTP的头信息不超出以下几种字段:** * Accept * Accept-Language * Content-Language * Last-Event-ID * Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain 若不满足以上条件,就属于非简单请求了。 * **简单请求过程:** * 对于简单请求,浏览器会直接发出CORS请求,它会在请求的头信息中增加一个Orign字段,该字段用来说明本次请求来自哪个源(协议+端口+域名),服务器会根据这个值来决定是否同意这次请求。如果Orign指定的域名在许可范围之内,服务器返回的响应就会多出以下信息头: ``` Access-Control-Allow-Origin: http://api.bob.com // 和Orign一直 Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值 Content-Type: text/html; charset=utf-8 // 表示文档类型 ``` * 如果Orign指定的域名不在许可范围之内,服务器会返回一个正常的HTTP回应,浏览器发现没有上面的Access-Control-Allow-Origin头部信息,就知道出错了。这个错误无法通过状态码识别,因为返回的状态码可能是200。 * **在简单请求中,在服务器内,至少需要设置字段:**`**Access-Control-Allow-Origin**` * **非简单请求过程** * 非简单请求是对服务器有特殊要求的请求,比如请求方法为DELETE或者PUT等。非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求,**称为预检请求**。 * 浏览器会询问服务器,当前所在的网页是否在服务器允许访问的范围内,以及可以使用哪些HTTP请求方式和头信息字段,只有得到肯定的回复,才会进行正式的HTTP请求,否则就会报错。 * 预检请求使用的**请求方法是OPTIONS**,表示这个请求是来询问的。他的头信息中的关键字段是Orign,表示请求来自哪个源。除此之外,头信息中还包括两个字段: * **Access-Control-Request-Method**:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法。 * **Access-Control-Request-Headers**: 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。 * 服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错。 * 服务器回应的CORS的字段如下: ``` Access-Control-Allow-Origin: http://api.bob.com // 允许跨域的源地址 Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法 Access-Control-Allow-Headers: X-Custom-Header // 服务器支持的所有头信息字段 Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie Access-Control-Max-Age: 1728000 // 用来指定本次预检请求的有效期,单位为秒 ``` * 只要服务器通过了预检请求,在以后每次的CORS请求都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。 * **在非简单请求中,至少需要设置以下字段:** ``` 'Access-Control-Allow-Origin' 'Access-Control-Allow-Methods' 'Access-Control-Allow-Headers' ``` * CORS中Cookie相关问题: * 在CORS请求中,如果想要传递Cookie,就要满足以下三个条件: * 在请求中设置 withCredentials * 默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie. ``` // 原生 xml 的设置方式 var xhr = new XMLHttpRequest(); xhr.withCredentials = true; // axios 设置方式 axios.defaults.withCredentials = true; ``` * **Access-Control-Allow-Credentials 设置为 true** * **Access-Control-Allow-Origin 设置为非** `*****` ### JSONP * **jsonp** 的原理就是利用`<script>`标签没有跨域限制,通过`<script>`标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。 ### postMessage 跨域 * postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一。 ### nginx代理跨域 * nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。 * nginx配置解决iconfont跨域 * 浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。 ``` location / { add_header Access-Control-Allow-Origin *; } ``` * nginx反向代理接口跨域 * 跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。 * 实现思路:通过Nginx配置一个代理服务器域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问。 ``` #nginx具体配置: proxy服务器 server { listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用 add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为* add_header Access-Control-Allow-Credentials true; } } ``` ### nodejs 中间件代理跨域 * node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。 ### location.hash + iframe跨域 * 实现原理:a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。 ### window.name + iframe跨域 * window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。 ### WebSocket协议跨域 * WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。 * 原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。 ### 正向代理和反向代理的区别 * **正向代理:** * 客户端想获得一个服务器的数据,但是因为种种原因无法直接获取。于是客户端设置了一个代理服务器,并且指定目标服务器,之后代理服务器向目标服务器转交请求并将获得的内容发送给客户端。这样本质上起到了对真实服务器隐藏真实客户端的目的。实现正向代理需要修改客户端,比如修改浏览器配置。 * **反向代理:** * 服务器为了能够将工作负载分不到多个服务器来提高网站性能 (负载均衡)等目的,当其受到请求后,会首先根据转发规则来确定请求应该被转发到哪个服务器上,然后将请求转发到对应的真实服务器上。这样本质上起到了对客户端隐藏真实服务器的作用。 一般使用反向代理后,需要通过修改 DNS 让域名解析到代理服务器 IP,这时浏览器无法察觉到真正服务器的存在,当然也就不需要修改配置了。 * 两者区别如图示: ![](https://img.kancloud.cn/06/a7/06a76f5deea8c530ff57a8b9639d1b8a_522x660.png) * 正向代理和反向代理的结构是一样的,都是 client-proxy-server 的结构,它们主要的区别就在于中间这个 proxy 是哪一方设置的。在正向代理中,proxy 是 client 设置的,用来隐藏 client;而在反向代理中,proxy 是 server 设置的,用来隐藏 server。 ### Nginx的概念及其工作原理 * Nginx 是一款轻量级的 Web 服务器,也可以用于反向代理、负载平衡和 HTTP 缓存等。Nginx 使用异步事件驱动的方法来处理请求,是一款面向性能设计的 HTTP 服务器。 * 传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于event-driven模型的。正是这个主要的区别带给了 Nginx 在性能上的优势。 * Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其他的 worker process,这一点和Apache 非常像,但是 Nginx 的 worker process 可以同时处理大量的HTTP请求,而每个 Apache process 只能处理一个。 ## 浏览器事件机制 ### 对事件委托的理解 * 事件委托本质上是利用了**浏览器事件冒泡**的机制。因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件委托(事件代理)。 * 使用事件委托可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理还可以实现事件的动态绑定,比如说新增了一个子节点,并不需要单独地为它添加一个监听事件,它绑定的事件会交给父元素中的监听函数来处理。 ### 同步和异步的区别 * **同步**指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,那么这个进程会一直等待下去,直到消息返回为止再继续向下执行。 * **异步**指的是当一个进程在执行某个请求时,如果这个请求需要等待一段时间才能返回,这个时候进程会继续往下执行,不会阻塞等待消息的返回,当消息返回时系统再通知进程进行处理。 ### 对事件循环的理解 * 因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。 ![](https://img.kancloud.cn/1c/e8/1ce8c5fb1b706a12e19451c57e7a18ba_1053x424.png) * Event Loop 执行顺序如下所示: * 首先执行同步代码,这属于宏任务 * 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行 * 执行所有微任务 * 当执行完所有微任务后,如有必要会渲染页面 * 然后开始下一轮 Event Loop,执行宏任务中的异步代码 ### 宏任务和微任务分别有哪些 * 微任务包括: promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。 * 宏任务包括: script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定时事件,还有如 I/O 操作、UI 渲染等。 ## 浏览器垃圾回收机制 ### V8的垃圾回收机制是怎样的 V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制。因此,V8 将内存(堆)分为新生代和老生代两部分。 * **新生代算法** * 新生代中的对象一般存活时间较短,使用 Scavenge GC 算法。 * 在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。 **老生代算法** * 老生代中的对象一般存活时间较长且数量也多,使用了两个算法,分别是标记清除算法和标记压缩算法。 * 先来说下什么情况下对象会出现在老生代空间中: * 新生代中的对象是否已经经历过一次 Scavenge 算法,如果经历过的话,会将对象从新生代空间移到老生代空间中。 * To 空间的对象占比大小超过 25 %。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代空间中。 ### 哪些操作会造成内存泄漏? * 第一种情况是由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。 * 第二种情况是设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收。 * 第三种情况是获取一个 DOM 元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回收。 * 第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。