浏览器与网络相关知识点回顾(前端进阶篇)

1.1 说说浏览器和 Node 事件循环的区别?
  • 浏览器
    • 浏览器环境下的 异步任务 分为 宏任务(macroTask) 和 微任务(microTask)
      • 常见的 task(宏任务) 比如:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等。
      • 常见的 micro-task 比如: new Promise().then(回调)、MutationObserver(html5新特性) 等。
    • 当满足执行条件时,宏任务(macroTask) 和 微任务(microTask) 会各自被放入对应的队列:宏队列(Macrotask Queue) 和 微队列(Microtask Queue) 中等待执行。
    • 关于微任务和宏任务在浏览器的执行顺序是这样的:
      • 执行一个task(宏任务)
      • 执行完micro-task(微任务)
      • 如此循环往复下去
  • Node:
    • 在 Node 环境中 任务类型 相对就比浏览器环境下要复杂一些,即Node的事件循环是libuv实现的,引用一张官网的图:
      在这里插入图片描述
    • Node中大体的task(宏任务)执行顺序是这样的:
      • timers定时器:本阶段执行已经安排的setTimeout()和setInterval() 的回调函数。
      • pending callbacks待定回调:执行延迟到下一个循环迭代的 I/O 回调。
      • idle, prepare:仅系统内部使用。
      • poll 轮询:检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,它们由计时器和 setImmediate() 排定的之外),其余情况 node 将在此处阻塞。
      • check 检测:setImmediate() 回调函数在这里执行。
      • close callbacks 关闭的回调函数:一些准备关闭的回调函数,如:socket.on(‘close’, …)。
    • 微任务和宏任务在Node的执行顺序
      • Node10以前:
        • 执行完一个阶段的所有任务
        • 执行完nextTick队列里面的内容
        • 然后执行完微任务队列的内容
      • Node 11以后: 和浏览器的行为统一了,都是每执行一个宏任务就执行完微任务队列。
  • 总结
    • 事件循环在 浏览器 和 Node 中的区别很容易被人忽视,执行顺序整理如下:
      • 浏览器环境下:
      while (true) {
          宏任务队列.shift();
          微任务队列全部任务();
      }
      
      • Node 环境下:
      while (true) {
          loop.forEach((阶段) => {
              阶段全部任务();
              nextTick全部任务();
              microTask全部任务();
          });
          loop = loop.next;
      }
      
1.2 浏览器缓存读取的规则有哪些?
  • 对于浏览器端的缓存来讲,这些规则是在HTTP协议头和HTML页面的Meta标签中定义的。他们分别从新鲜度和校验值两个维度来规定浏览器是否可以直接使用缓存中的副本,还是需要去源服务器获取更新的版本。
    • 新鲜度(过期机制):也就是缓存副本有效期。一个缓存副本必须满足以下条件,浏览器会认为它是有效的,足够新的:
      • 含有完整的过期时间控制头信息(HTTP协议报头),并且仍在有效期内;
      • 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度;
      • 满足以上两个情况的一种,浏览器会直接从缓存中获取副本并渲染。
    • 校验值(验证机制):服务器返回资源的时候有时在控制头信息带上这个资源的实体标签Etag(Entity Tag),它可以用来作为浏览器再次请求过程的校验标识。如果发现校验标识不匹配,说明资源已经被修改或过期,浏览器需要重新获取资源内容。
1.3 浏览器缓存的控制有哪些?
  • 使用HTML Meta 标签

    • Web开发者可以在HTML页面的<head>节点中加入<meta>标签
      <META HTTP-EQUIV="Pragma" CONTENT="no-cache"> 
      
    • 上述代码的作用是告诉浏览器当前页面不被缓存,每次访问都需要去服务器拉取。使用上很简单,但只有部分浏览器可以支持,而且所有缓存代理服务器都不支持,因为代理不解析HTML内容本身。
    • 可以通过这个页面测试你的浏览器是否支持:Pragma No-Cache Test 。
  • 使用缓存有关的HTTP消息报头

    • 一个URI的完整HTTP协议交互过程是由HTTP请求和HTTP响应组成的。有关HTTP详细内容可参考《Hypertext Transfer Protocol — HTTP/1.1》、《HTTP协议详解》等。
    • 在HTTP请求和响应的消息报头中,常见的与缓存有关的消息报头有:
      • Cache-Control与Expires:
        • Cache-Control与Expires的作用一致,都是指明当前资源的有效期,控制浏览器是否直接从浏览器缓存取数据还是重新发请求到服务器取数据。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。
      • Last-Modified/ETag与Cache-Control/Expires:
        • 配置Last-Modified/ETag的情况下,浏览器再次访问统一URI的资源,还是会发送请求到服务器询问文件是否已经修改,如果没有,服务器会只发送一个304回给浏览器,告诉浏览器直接从自己本地的缓存取数据;如果修改过那就整个数据重新发给浏览器;
        • Cache-Control/Expires则不同,如果检测到本地的缓存还是有效的时间范围内,浏览器直接使用本地副本,不会发送任何请求。两者一起使用时,Cache-Control/Expires的优先级要高于Last-Modified/ETag。即当本地副本根据Cache-Control/Expires发现还在有效期内时,则不会再次发送请求去服务器询问修改时间(Last-Modified)或实体标识(Etag)了。
        • 一般情况下,使用Cache-Control/Expires会配合Last-Modified/ETag一起使用,因为即使服务器设置缓存时间, 当用户点击“刷新”按钮时,浏览器会忽略缓存继续向服务器发送请求,这时Last-Modified/ETag将能够很好利用304,从而减少响应开销。
      • Last-Modified与ETag:
        • 你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:
          • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的新鲜度
          • 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存
          • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
        • Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。Etag的服务器生成规则和强弱Etag的相关内容可以参考,《互动百科-Etag》和《HTTP Header definition》,这里不再深入。
      • 用户操作行为与缓存:
        • 用户在使用浏览器的时候,会有各种操作,比如输入地址后回车,按F5刷新等,这些行为会对缓存有什么影响呢?
        • 通过上表我们可以看到,当用户在按F5进行刷新的时候,会忽略Expires/Cache-Control的设置,会再次发送请求去服务器请求,而Last-Modified/Etag还是有效的,服务器会根据情况判断返回304还是200;而当用户使用Ctrl+F5进行强制刷新的时候,只是所有的缓存机制都将失效,重新从服务器拉去资源。
1.4 哪些请求不能被缓存?
  • 无法被浏览器缓存的请求:
    • HTTP信息头中包含Cache-Control:no-cache,pragma:no-cache,或Cache-Control:max-age=0等告诉浏览器不用缓存的请求
    • 需要根据Cookie,认证信息等决定输入内容的动态请求是不能被缓存的
    • 经过HTTPS安全加密的请求(有人也经过测试发现,ie其实在头部加入Cache-Control:max-age信息,firefox在头部加入Cache-Control:Public之后,能够对HTTPS的资源进行缓存,参考《HTTPS的七个误解》)
    • POST请求无法被缓存
    • HTTP响应头中不包含Last-Modified/Etag,也不包含Cache-Control/Expires的请求无法被缓存
1.5 为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?
  • 英文术语叫:image beacon 在Google 的 Make the Web Faster 的 #Track web traffic in the background 中有提到。 主要应用于只需要向服务器发送数据(日志数据)的场合,且无需服务器有消息体回应。比如收集访问者的统计信息。
  • 一般做法是服务器用一个1x1的gif图片来作为响应,当然这有点浪费服务器资源。因此用header来响应比较合适,目前比较合适的做法是服务器发送"204 No Content",即“服务器成功处理了请求,但不需要返回任何实体内容”。
  • 另外该脚本的位置一般放在页面最后以免阻塞页面渲染,并且一般情况下也不需要append到DOM中。通过它的onerror和onload事件来检测发送状态。
    <script type="text/javascript">
     var thisPage = location.href;
     var referringPage = (document.referrer) ? document.referrer : "none";
     var beacon = new Image();
     beacon.src = "http://www.example.com/logger/beacon.gif?page=" + encodeURI(thisPage)
     + "&ref=" + encodeURI(referringPage);
    </script>
    
  • 总结:
    • 能够完成整个 HTTP 请求+响应(尽管不需要响应内容)
    • 触发 GET 请求之后不需要获取和处理数据、服务器也不需要发送数据
    • 跨域友好
    • 执行过程无阻塞
    • 相比 XMLHttpRequest 对象发送 GET 请求,性能上更好
    • GIF的最低合法体积最小(最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字节)
1.6 如何解决移动端 Retina 屏 1px 像素问题?
  • 造成边框变粗的原因:因为css中的1px并不等于移动设备的1px,这些由于不同的手机有不同的像素密度。在window对象中有一个devicePixelRatio属性,他可以反应css中的像素与设备的像素比。

  • 使用0.5px边框实现:

    • 在2014年的 WWDC,“设计响应的Web体验” 一讲中,Ted O’Connor 讲到关于“retinahairlines”(retina 极细的线):在retina屏上仅仅显示1物理像素的边框,开发者应该如何处理呢。他们曾介绍到 iOS 8 和 OS X Yosemite 即将支持 0.5px 的边框。但问题是 retina 屏的浏览器可能不认识0.5px的边框,将会把它解释成0px,没有边框。包括 iOS 7 和之前版本,OS X Mavericks 及以前版本,还有 Android 设备。

    • 解决方案:通过 JavaScript 检测浏览器能否处理0.5px的边框,如果可以,给html标签元素添加个class。

      if (window.devicePixelRatio && devicePixelRatio >= 2) {
        var testElem = document.createElement('div');
        testElem.style.border = '.5px solid transparent';
        document.body.appendChild(testElem);
      }
      if (testElem.offsetHeight == 1) {
        document.querySelector('html').classList.add('hairlines');
      }
        document.body.removeChild(testElem);
      }
      // 脚本应该放在内,如果在里面运行,需要包装 $(document).ready(function() {})
      
      div {
        border: 1px solid #bbb;
      }
      .hairlines div {
        border-width: 0.5px;
      }
      
    • 优点:简单,不需要过多代码。

    • 缺点:无法兼容安卓设备、 iOS 8 以下设备。

  • 使用border-image实现:

    • 首先准备一张符合你要求的border-image
    • 样式设置:
      .border-bottom-1px {
        border-width: 0 0 1px 0;
        -webkit-border-image: url(linenew.png) 0 0 2 0 stretch;
        border-image: url(linenew.png) 0 0 2 0 stretch;
      }
      
    • 上面是把border设置在边框的底部,所以使用的图片是2px高,上部的1px颜色为透明,下部的1px使用视觉规定的border的颜色。如果边框底部和顶部同时需要border,可以使用下面的border-image样式设置
      .border-image-1px {
        border-width: 1px 0;
        -webkit-border-image: url(linenew.png) 2 0 stretch;
        border-image: url(linenew.png) 2 0 stretch;
      }
      
    • 到目前为止,我们已经能在iphone上展现1px border的效果了。但是我们发现这样的方法在非视网膜屏上会出现border显示不出来的现象,于是使用Media Query做了一些兼容,样式设置如下:
      .border-image-1px {
        border-bottom: 1px solid #666;
      }
      @media only screen and (-webkit-min-device-pixel-ratio: 2) {
        .border-image-1px {
          border-bottom: none;
          border-width: 0 0 1px 0;
          -webkit-border-image: url(../img/linenew.png) 0 0 2 0 stretch;
          border-image: url(../img/linenew.png) 0 0 2 0 stretch;
        }
      }
      
    • 优点:
      • 可以设置单条,多条边框
      • 没有性能瓶颈的问题
    • 缺点:
      • 修改颜色麻烦, 需要替换图片
      • 圆角需要特殊处理,并且边缘会模糊
  • 使用background-image实现:

    • background-image 跟border-image的方法一样,你要先准备一张符合你要求的图片。然后将边框模拟在背景上。
    • 样式设置:
      .background-image-1px {
        background: url(../img/line.png) repeat-x left bottom;
        -webkit-background-size: 100% 1px;
        background-size: 100% 1px;
      }
      
    • 优点:
      • 可以设置单条,多条边框
      • 没有性能瓶颈的问题
    • 缺点:
      • 修改颜色麻烦, 需要替换图片
      • 圆角需要特殊处理,并且边缘会模糊
  • 多背景渐变实现:

    • 与background-image方案类似,只是将图片替换为css3渐变。设置1px的渐变背景,50%有颜色,50%透明。
    • 样式设置:
      .background-gradient-1px {
        background:
          linear-gradient(#000, #000 100%, transparent 100%) left / 1px 100% no-repeat,
          linear-gradient(#000, #000 100%, transparent 100%) right / 1px 100% no-repeat,
          linear-gradient(#000,#000 100%, transparent 100%) top / 100% 1px no-repeat,
          linear-gradient(#000,#000 100%, transparent 100%) bottom / 100% 1px no-repeat
      }
      /* 或者 */
      .background-gradient-1px{
        background:
          -webkit-gradient(linear, left top, right bottom, color-stop(0, transparent), color-stop(0, #000), to(#000)) left / 1px 100% no-repeat,
          -webkit-gradient(linear, left top, right bottom, color-stop(0, transparent), color-stop(0, #000), to(#000)) right / 1px 100% no-repeat,
          -webkit-gradient(linear, left top, right bottom, color-stop(0, transparent), color-stop(0, #000), to(#000)) top / 100% 1px no-repeat,
          -webkit-gradient(linear, left top, right bottom, color-stop(0, transparent), color-stop(0, #000), to(#000)) bottom / 100% 1px no-repeat
      }
      
    • 优点:
      • 可以实现单条、多条边框
      • 边框的颜色随意设置
    • 缺点:
      • 代码量不少
      • 圆角没法实现
      • 多背景图片有兼容性问题
  • 使用box-shadow模拟边框:

    • 利用css 对阴影处理的方式实现0.5px的效果
    • 样式设置:
      .box-shadow-1px {
        box-shadow: inset 0px -1px 1px -1px #c8c7cc;
      }
      
    • 优点:
      • 代码量少
      • 可以满足所有场景
    • 缺点:
      • 边框有阴影,颜色变浅
  • viewport + rem 实现:

    • 同时通过设置对应viewport的rem基准值,这种方式就可以像以前一样轻松愉快的写1px了
    • 在devicePixelRatio = 2 时,输出viewport:
      <meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
      
    • 在devicePixelRatio = 3 时,输出viewport:
      <meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">
      
    • 这种兼容方案相对比较完美,适合新的项目,老的项目修改成本过大。
    • 优点:
      • 所有场景都能满足
      • 一套代码,可以兼容基本所有布局
    • 缺点:
      • 老项目修改代价过大,只适用于新项目
  • 伪类 + transform 实现:

    • 对于老项目,有没有什么办法能兼容1px的尴尬问题了,个人认为伪类+transform是比较完美的方法了。
    • 原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。
    • 单条border样式设置:
      .scale-1px{
        position: relative;
        border:none;
      }
      .scale-1px:after{
        content: '';
        position: absolute;
        bottom: 0;
        background: #000;
        width: 100%;
        height: 1px;
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
        -webkit-transform-origin: 0 0;
        transform-origin: 0 0;
      }
      
    • 四条boder样式设置:
      .scale-1px{
        position: relative;
        margin-bottom: 20px;
        border:none;
      }
      .scale-1px:after{
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        border: 1px solid #000;
        -webkit-box-sizing: border-box;
        box-sizing: border-box;
        width: 200%;
        height: 200%;
        -webkit-transform: scale(0.5);
        transform: scale(0.5);
        -webkit-transform-origin: left top;
        transform-origin: left top;
      }
      
    • 最好在使用前也判断一下,结合 JS 代码,判断是否 Retina 屏:
      if(window.devicePixelRatio && devicePixelRatio >= 2){
        document.querySelector('ul').className = 'scale-1px';
      }
      
    • 优点:
      • 所有场景都能满足
      • 支持圆角(伪类和本体类都需要加border-radius)
    • 缺点:
      • 对于已经使用伪类的元素(例如clearfix),可能需要多层嵌套
1.7 什么是 DOM 和 BOM?
  • BOM是browser object model的缩写,简称浏览器对象模型。是用来获取或设置浏览器的属性、行为,例如:新建窗口、获取屏幕分辨率、浏览器版本号等。 比如 alert();弹出一个窗口,这属于BOM。
  • DOM是Document ,简称文档对象模型。是用来获取或设置文档中标签的属性,例如获取或者设置input表单的value值。document.getElementById("").value; 这属于DOM。
  • BOM的内容不多,主要还是DOM。 由于DOM的操作对象是文档(Document),所以dom和浏览器没有直接关系。
1.8 三种事件模型是什么?
  • DOM0事件模型(原始事件模型)

    • 有两种实现方式:
      • 通过元素属性来绑定事件
        <button onclick="click()">点我</button>
        
      • 先获取页面元素,然后以赋值的形式来绑定事件
        const btn = document.getElementById('btn')
        btn.onclick = function(){
            //do something
        }
        //解除事件
        btn.onclick = null
        
    • DOM0缺点:一个dom节点只能绑定一个事件,再次绑定将会覆盖之前的事件。
  • DOM2事件模型

    • dom2新增冒泡和捕获的概念,并且支持一个元素节点绑定多个事件。
    • 事件捕获和事件冒泡(capture,bubble ):
      在这里插入图片描述
      • 如图所示1,2,3为捕获,4,5,6,7为冒泡,也就是说事件流分为三个阶段:
        • 事件捕获阶段:事件从 document 向下传播到目标元素,依次检查所有节点是否绑定了监听事件,如果有则执行。
        • 事件处理阶段:事件在达到目标元素时,触发监听事件。
        • 事件冒泡阶段:事件从目标元素冒泡到 document,并且一次检查各个节点是否绑定了监听函数,如果有则执行。
    • addEventListener:
      • addEventListener有三个参数 事件名称、事件回调、捕获/冒泡
        btn.addEventListener('click',function(){
            console.log('btn')
        },true)
        box.addEventListener('click',function(){
            console.log('box')
        },false)
        
      • 设置为true,则事件在捕获阶段执行,为false则在冒泡阶段执行。
  • IE事件模型

    • IE事件只支持冒泡,所以事件流有两个阶段:
      • 事件处理阶段:事件在达到目标元素时,触发监听事件
      • 事件冒泡阶段:事件从目标元素冒泡到 document,并且一次检查各个节点是否绑定了监听函数,如果有则执行
    • 实现方法:
      // 绑定事件
      el.attachEvent(eventType, handler)
      
      // 移除事件
      el.detachEvent(eventType, handler)
      
    • 改事件模型只在IE中有效,不兼容其他浏览器,所以大家了解一下就行。。。
1.9 事件委托是什么?
  • 事件委托也称之为事件代理(Event Delegation)。是JavaScript中常用绑定事件的常用技巧。顾名思义,“事件代理”即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
    • 举个通俗的例子:比如一个宿舍的同学同时快递到了,一种方法就是他们一个个去领取,还有一种方法就是把这件事情委托给宿舍长,让一个人出去拿好所有快递,然后再根据收件人一 一分发给每个宿舍同学;
    • 这里,取快递就是一个事件,每个同学指的是需要响应事件的 DOM 元素,而出去统一领取快递的宿舍长就是代理的元素,所以真正绑定事件的是这个元素,按照收件人分发快递的过程就是在事件执行中,需要判断当前响应的事件应该匹配到被代理元素中的哪一个或者哪几个。
  • 一个事件触发后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段:
    • 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
    • 目标阶段:在目标节点上触发,称为“目标阶段”;
    • 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层。
  • 事件委托的优点:
    • 可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件就非常棒。
      <ul id="list">
      <li>item 1</li>
      <li>item 2</li>
      <li>item 3</li>
      ......
      <li>item n</li>
      </ul>
      
      • 上面代码所示,如果给每个li列表项都绑定一个函数,那对内存的消耗是非常大的,因此较好的解决办法就是将li元素的点击事件绑定到它的父元素ul身上,执行事件的时候再去匹配判断目标元素。
    • 可以实现当新增子对象时无需再次对其绑定(动态绑定事件):
      • 上述的例子中列表项li就几个,我们给每个列表项都绑定了事件; 很多时候,我们需要通过 AJAX 或者用户操作动态的增加或者删除列表项li元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的;所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。
      • 使用事件委托注意事项:使用“事件委托”时,并不是说把事件委托给的元素越靠近顶层就越好。事件冒泡的过程也需要耗时,越靠近顶层,事件的”事件传播链”越长,也就越耗时。如果DOM嵌套结构很深,事件冒泡通过大量祖先元素会导致性能损失。
  • 如何阻止默认动作:
    • 有一些html元素默认的行为,比如说a标签,点击后有跳转动作;form表单中的submit类型的input有一个默认提交跳转事件;reset类型的input有重置表单行为。
    • JavaScript提供了方法 :
      var $a = document.getElementsByTagName("a")[0];
      $a.onclick = function(e){
      	alert("跳转动作被我阻止了")
      	e.preventDefault();
      	//return false;//也可以但仅仅是在HTML事件属性 和 DOM0级事件处理方法中,才能通过返回 return false 的形式组织事件宿主的默认行为。
      }
      
  • 怎么阻止冒泡事件:
    function stopBubble(e){
    	if(e&&e.stopPropagation){//非IE
    		e.stopPropagation();
    	}else{//IE
    		window.event.cancelBubble=true;
    	}
    }
    
1.10 谈一谈浏览器的缓存机制?
  • 概述:浏览器的缓存机制也就是我们说的HTTP缓存机制,其机制是根据HTTP报文的缓存标识进行的,所以在分析浏览器缓存机制之前,我们先使用图文简单介绍一下HTTP报文,HTTP报文分为两种:

    • HTTP请求(Request)报文,报文格式为:请求行 – HTTP头(通用信息头,请求头,实体头) – 请求报文主体(只有POST才有报文主体),如下图:
      在这里插入图片描述
      在这里插入图片描述

    • HTTP响应(Response)报文,报文格式为:状态行 – HTTP头(通用信息头,响应头,实体头) – 响应报文主体,如下图:
      在这里插入图片描述
      在这里插入图片描述

    • 注:通用信息头指的是请求和响应报文都支持的头域,分别为Cache-Control、Connection、Date、Pragma、Transfer-Encoding、Upgrade、Via;实体头则是实体信息的实体头域,分别为Allow、Content-Base、Content-Encoding、Content-Language、Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、Etag、Expires、Last-Modified、extension-header。这里只是为了方便理解,将通用信息头,响应头/请求头,实体头都归为了HTTP头。

  • 缓存过程分析:浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:
    在这里插入图片描述

    • 由上图我们可以知道:
      • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
      • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
    • 以上两点结论就是浏览器缓存机制的关键,他确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了
  • 为了方便理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存:

    • 强制缓存:强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种(暂不分析协商缓存过程),如下:

      • 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致),如下图:
        在这里插入图片描述
      • 存在该缓存结果和缓存标识,但是结果已经失效,强制缓存失效,则使用协商缓存(暂不分析),如下图:
        在这里插入图片描述
      • 存在该缓存结果和缓存标识,且该结果没有还没有失效,强制缓存生效,直接返回该结果,如下图:
        在这里插入图片描述
    • 强制缓存的缓存规则:当浏览器向服务器发送请求的时候,服务器会将缓存规则放入HTTP响应的报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Conctrol的优先级比Expires高。

      • Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求的结果缓存的到期时间,即再次发送请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。 到了HTTP/1.1,Expires已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间由于某些原因(时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存直接失效,那么强制缓存存在的意义就毫无意义。

      • 在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:

        • public:所有内容都将被缓存(客户端和代理服务器都可缓存)
        • private:所有内容只有客户端可以缓存,Cache-Control的默认取值
        • no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
        • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
        • max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
      • 直接看一个例子,如下:
        在这里插入图片描述

        • HTTP响应报文中expires的时间值,是一个绝对值
        • HTTP响应报文中Cache-Control为max-age=600,是相对值
      • 由于Cache-Control的优先级比expires,那么直接根据Cache-Control的值进行缓存,意思就是说在600秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。

      • 注:在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于expires是更好的选择,所以同时存在时,只有Cache-Control生效。

      • 浏览器的缓存存放在哪里,如何在浏览器中判断强制缓存是否生效?
        在这里插入图片描述

        • 以博客的请求为例,状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存存放的位置,分别为from memory cache 和 from disk cache。
      • 那么from memory cache 和 from disk cache又分别代表的是什么呢?什么时候会使用from disk cache,什么时候会使用from memory cache呢?

        • from memory cache代表使用内存中的缓存,from disk cache则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory –> disk。
        • 过程如下:
          • 访问博客网站
            在这里插入图片描述
          • 关闭博客的标签页重新打开博客
            在这里插入图片描述
          • 刷新
            在这里插入图片描述
          • 最后一个步骤刷新的时候为什么不是同时存在着from disk cache和from memory cache?
          • 对于这个问题,我们需要了解内存缓存(from memory cache)和硬盘缓存(from disk cache),如下:
            • 内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取和时效性:
              • 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
              • 时效性:一旦该进程关闭,则该进程的内存则会清空。
            • 硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
              • 在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);而css文件则会存入硬盘文件中,所以每次渲染页面都需要从硬盘读取缓存(from disk cache)。
    • 协商缓存:协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

      • 协商缓存生效,返回304,如下:
        在这里插入图片描述
      • 协商缓存失败,返回200和请求结果,如下:
        在这里插入图片描述
      • 同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。
        • Last-Modified:Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间,如下:
          在这里插入图片描述
        • If-Modified-Since:If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件,如下:
          在这里插入图片描述
        • Etag:Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),如下:
          在这里插入图片描述
        • If-None-Match:If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200,如下:
          在这里插入图片描述
        • :Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。
  • 总结

    • 强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:
      在这里插入图片描述
1.11 什么是浏览器的同源政策?
  • 概念:同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
    • 同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源;
    • 下表给出了相对http://store.company.com/dir/page.html同源检测的示例:
      在这里插入图片描述
    • 所限制的跨域交互包括:
      • Cookie、LocalStorage、IndexdDB 等存储内容;
      • DOM 节点;
      • Ajax 请求;
    • 浏览器中的大部分内容都是受同源策略限制的,但是以下三个标签可以不受限制:
      <img src=XXX>
      <link href=XXX>
      <script src=XXX>	
      
1.12 如何解决跨域问题?
  • 跨域的解决方案:

    • jsonp:只支持 GET,不支持 POST 请求,不安全 XSS
    • cors:需要后台配合进行相关的设置
    • postMessage:配合使用 iframe,需要兼容 IE6、7、8、9
    • document.domain:仅限于同一域名下的子域
    • websocket:需要后台配合修改协议,不兼容,需要使用 http://socket.io
    • proxy:使用代理去避开跨域请求,需要修改 nginx、apache 等的配置
  • JSONP 实现:JSONP(JSON with Padding(填充))是 JSON 的一种“使用模式”,本质不是 Ajax 请求,是 script 标签请求。JSONP 请求本质上是利用了 “Ajax 请求会受到同源策略限制,而 script 标签请求不会” 这一点来绕过同源策略。

    • 简单 JSONP 实现:
      class Jsonp {
        constructor(req) {
          this.url = req.url;
          this.callbackName = req.callbackName;
        }
        create() {
          const script = document.createElement("script");
          const url = `${this.url}?callback=${this.callbackName}`;
          script.src = url;
          document.getElementsByTagName("head")[0].appendChild(script);
        }
      }
      
      new Jsonp({
        url: "http://127.0.0.1:8000/",
        callbackName: "getMsg"
      }).create();
      
      function getMsg(data) {
        data = JSON.parse(data);
        console.log(`My name is ${data.name}, and ${data.age} years old.`);
      }
      
    • 服务端(Node):
      const http = require("http");
      const querystring = require("querystring");
      
      const server = http.createServer((req, res) => {
        const url = req.url;
        const query = querystring.parse(url.split("?")[1]);
        const { callback } = query;
        const data = {
          name: "Yang Min",
          age: "8"
        };
        res.end(`${callback}('${JSON.stringify(data)}')`);
      });
      
      server.listen(8000);
      
    • 前端利用 http-server -p 8001 .,开启一个服务,然后 Node 也开启一个端口为 8000 的服务,运行:
      My name is Yang Min, and 8 years old.
      
    • 一个 JSONP 的步骤实质:
      • 客户端发送 script 请求,参数中带着处理返回数据的回调函数的名字 (通常是 callback),如请求 script 的 url 是:
        http://127.0.0.1:8000/?callback=getMsg
        
      • 服务端收到请求,以回调函数名和返回数据组成立即执行函数的字符串,比如:其中 callback 的值是客户端发来的回调函数的名字,假设回调函数的名字是 getMsg,返回脚本的内容就是:
        getMsg("{name: 'Yang Min', age: '8'}");
        
      • 客户端收到 JavaScript 脚本内容后,立即执行脚本,这样就实现了获取跨域服务器数据的目的。
      • 很明显,由于 JSONP 技术本质上利用了 script 脚本请求,所以只能实现 GET 跨域请求,这也是 JSONP 跨域的最大限制。
      • 由于 server 产生的响应为 json 数据的包装(故称之为 jsonp,即 json padding),形如:
        getMsg("{name: 'Yang Min', age: '8'}")
        
    • JSONP 封装
      • 客户端:
        const jsonp = ({ url, params, callbackName }) => {
          const generateURL = () => {
            let dataStr = "";
            for (let key in params) {
              dataStr += `${key}=${params[key]}&`;
            }
            dataStr += `callback=${callbackName}`;
            return `${url}?${dataStr}`;
          };
          return new Promise((resolve, reject) => {
            // 初始化回调函数名称
            callbackName =
              callbackName ||
              "cb" +
                Math.random()
                  .toString()
                  .replace(".", "");
            let scriptEle = document.createElement("script");
            scriptEle.src = generateURL();
            document.body.appendChild(scriptEle);
        
            // 绑定到 window 上,为了后面调用
            window[callbackName] = data => {
              resolve(data);
              // script 执行完了,成为无用元素,需要清除
              document.body.removeChild(scriptEle);
            };
          });
        };
        
        jsonp({
          url: "http://127.0.0.1:8000/",
          params: {
            name: "Yang Min",
            age: "8"
          },
          callbackName: "getData"
        })
          .then(data => JSON.parse(data))
          .then(data => {
            console.log(data); // {name: "Yang Min", age: "8"}
          });
        
      • Node 端:
        const http = require("http");
        const querystring = require("querystring");
        
        const server = http.createServer((req, res) => {
          const url = req.url;
          const query = querystring.parse(url.split("?")[1]);
          const { name, age, callback } = query;
          const data = {
            name,
            age
          }
          res.end(`${callback}('${JSON.stringify(data)}')`);
        });
        
        server.listen(8000);
        
    • jQuery 中的 JSONP
      • Node 部分不变,使用 jQuery(3.4.1) 如下:
        function getAjaxData() {
          $.ajax({
            type: "get",
            async: false,
            url: "http://127.0.0.1:8000/",
            dataType: "jsonp", //由 JSON 改为 JSONP
            jsonp: "callback", //传递给请求处理程序或页面的,标识jsonp回调函数名(一般为:callback)
            jsonpCallback: "getData", //callback的function名称,成功就会直接走 success 方法
            success: function(data) {
              data = JSON.parse(data);
              console.log(`My name is ${data.name}, and ${data.age} years old.`);
            },
            error: function() {
              console.log("Error");
            }
          });
        }
        getAjaxData();
        
      • 使用延迟对象重新写下:
        function getAjaxData() {
          const def = $.ajax({
            type: "get",
            async: false,
            url: "http://127.0.0.1:8000/",
            dataType: "jsonp",
            jsonp: "callback",
            jsonpCallback: "getData"
          });
        
          def
            .done(data => {
              data = JSON.parse(data);
              console.log(`My name is ${data.name}, and ${data.age} years old.`);
            })
            .fail(err => {
              console.log(err);
            });
        }
        
      • JSONP 缺点
        • 只支持 GET 请求
        • 只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面之间如何进行 JavaScript 调用的问题
        • 调用失败的时候不会返回各种 HTTP 状态码
        • 安全性,万一假如提供 JSONP 的服务存在页面注入漏洞,即它返回的 javascript 的内容被人控制的
  • 跨域资源共享 CORS :跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。 — MDN

    • 允许在下列场景中使用跨域 HTTP 请求:
      • 由 XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求
      • Web 字体 (CSS 中通过 @font-face 使用跨域字体资源)
      • WebGL 贴图
      • 使用 drawImage 将 Images/video 画面绘制到 canvas
    • 简单请求、非简单请求
      • 浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
      • 只要同时满足以下两大条件,就属于简单请求(不会触发 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 如何工作
      • 首先浏览器判断请求是简单请求还是复杂请求(非简单请求)
      • 如果是复杂请求,那么在进行真正的请求之前,浏览器会先使用 OPTIONS 方法发送一个预检请求 (preflight request),OPTIONS 是 HTTP/1.1 协议中定义的方法,用以从服务器获取更多信息
      • 该方法不会对服务器资源产生影响,预检请求中同时携带了下面两个首部字段:
        • Access-Control-Request-Method: 这个字段表明了请求的方法;
        • Access-Control-Request-Headers: 这个字段表明了这个请求的 Headers;
        • Origin: 这个字段表明了请求发出的域;
      • 服务端收到请求后,会以 Access-Control-* response headers 的形式对客户端进行回复:
        • Access-Control-Allow-Origin: 能够被允许发出这个请求的域名,也可以使用*来表明允许所有域名
        • Access-Control-Allow-Methods: 用逗号分隔的被允许的请求方法的列表
        • Access-Control-Allow-Headers: 用逗号分隔的被允许的请求头部字段的列表
        • Access-Control-Max-Age: 这个预检请求能被缓存的最长时间,在缓存时间内,同一个请求不会再次发出预检请求
    • 简单请求
      • 对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是在头信息之中,自动增加一个 Origin 字段,用来说明请求来自哪个源。服务器拿到请求之后,在回应时对应地添加Access-Control-Allow-Origin字段,如果 Origin 不在这个字段的范围中,那么浏览器就会将响应拦截。
      • Access-Control-Allow-Credentials。这个字段是一个布尔值,表示是否允许发送 Cookie,对于跨域请求,浏览器对这个字段默认值设为 false,而如果需要拿到浏览器的 Cookie,需要添加这个响应头并设为 true, 并且在前端也需要设置withCredentials属性:
        let xhr = new XMLHttpRequest();
        xhr.withCredentials = true;
        
      • Access-Control-Expose-Headers。这个字段是给 XMLHttpRequest 对象赋能,让它不仅可以拿到基本的 6 个响应头字段(包括Cache-Control、Content-Language、Content-Type、Expires、Last-Modified和Pragma), 还能拿到这个字段声明的响应头字段。比如这样设置:
        Access-Control-Expose-Headers: aaa
        
      • 那么在前端可以通过 XMLHttpRequest.getResponseHeader(‘aaa’) 拿到 aaa 这个字段的值。
1.13 简单介绍一下 V8 引擎的垃圾回收机制?
  • Chrome V8的垃圾回收机制:如何判断回收内容?如何确定哪些内存需要回收?哪些内存不需要回收?
    • 这是垃圾回收期需要解决的最基本问题。我们可以这样假定,一个对象为活对象当且仅当它被一个根对象 或另一个活对象指向。根对象永远是活对象,它是被浏览器或V8所引用的对象。被局部变量所指向的对象也属于根对象,因为它们所在的作用域对象被视为根对 象。全局对象(Node中为global,浏览器中为window)自然是根对象。浏览器中的DOM元素也属于根对象。
  • 如何识别指针和数据垃圾回收器需要面临一个问题:它需要判断哪些是数据?哪些是指针?
    • 由于很多垃圾回收算法会将对象在内存中移动(紧凑,减少内存碎片),所以经常需要进行指针的改写。目前主要有三种方法来识别指针:
      • 保守法:将所有堆上对齐的字都认为是指针,那么有些数据就会被误认为是指针。于是某些实际是数字的假指针,会背误认为指向活跃对象,导致内存泄露(假指针指向的对象可能是死对象,但依旧有指针指向——这个假指针指向它)同时我们不能移动任何内存区域。
      • 编译器提示法:如果是静态语言,编译器能够告诉我们每个类当中指针的具体位置,而一旦我们知道对象时哪个类实例化得到的,就能知道对象中所有指针。这是JVM实现垃圾回收的方式,但这种方式并不适合JS这样的动态语言。
      • 标记指针法:这种方法需要在每个字末位预留一位来标记这个字段是指针还是数据。这种方法需要编译器支持,但实现简单,而且性能不错。V8采用的是这种方式。V8将所有数据以32bit字宽来存储,其中最低一位保持为0,而指针的最低两位为01。
    • V8的回收策略自动垃圾回收算法的演变过程中出现了很多算法,但是由于不同对象的生存周期不同,没有一种算法适用于所有的情况。所以V8采用了一种分代回收的策 略,将内存分为两个生代:新生代和老生代。新生代的对象为存活时间较短的对象,老生代中的对象为存活时间较长或常驻内存的对象。分别对新生代和老生代使用 不同的垃圾回收算法来提升垃圾回收的效率。对象起初都会被分配到新生代,当新生代中的对象满足某些条件(后面会有介绍)时,会被移动到老生代(晋升)。
    • V8的分代内存默认情况下,64位环境下的V8引擎的新生代内存大小32MB、老生代内存大小为1400MB,而32位则减半,分别为16MB和700MB。V8内存的最大保留空间分别为1464MB(64位)和732MB(32位)。具体的计算公式是:4*reserved_semispace_space_ + max_old_generation_size_,新生代由两块reserved_semispace_space_组成,每块16MB(64位)或8MB(32位)。
1.14 哪些操作会造成内存泄漏?
  • 内存泄漏:就是没有使用,或已经使用完的变量,没有及时回收。
    • 常见的javascript内存泄漏:
      • 1.意外的全局变量

        • 初始化未经声明的变量,总是会创建一个全局变量
          	function f1(){
                  //初始化这个变量没有声明,成为全局变量,不会自动被回收
                  bar = "this is a global variable"
                  //window.bar = "this is a global variable"
              }
          
        • 由this创建的全局变量
          function f1(){
             this.bar = "this is a global variable"
          }
          f1()
          
        • 注意:有些全局变量产生的垃圾,不可回收,尤其当全局变量用于临时存储和处理大量信息的时候,确保用完之后将他设置为null。
      • 2.计时器或回调函数

        var someResource = getData()
           //这个列子说明只要存在节点才需要定时器和回调函数,此时定时器并没有回收(只有停止时才回收)
         setInterval(function(){
             var node = document.getElementById('Node');
             if(node){
                 node.innerHTML = JSON.stringify(someResource)
             }
         },1000)
        
        • 注意:一旦定时器不需要,需要移除
      • 3.dom清空或删除时,事件未清除导致的内存泄漏

        <div id="container"></div>
        $('#container').bind('click', function(){
            console.log('click');
        }).remove();
        // 事件没清除
        
        // 解决
        <div id="container"></div>
        $('#container').bind('click', function(){
            console.log('click');
        }).off('click').remove();
        //把事件清除了,即可从内存中移除
        
      • 4.闭包

        // 匿名函数可以访问父级作用域中的变量
        function assignHandler(){
            var element = document.getElementById('someElement') //该变量被闭包引用,引用次数至少为1,不会被回收
            element.onclick=function(){
                alert(element.id)
            }
        }
        
        function assignHandler(){
            var element = document.getElementById('someElement')
            var id = element.id
            element.onclick=function(){
                alert(id)
            }
            element=null
        }
        
      • 5.子元素存在引起的内存泄漏
        在这里插入图片描述

        • 如上图所示:
          • 黄色是直接被js变量所引用的,存在内存中
          • 引用A的时候再间接引用B
          • 子元素 B 由于 parentNode 的间接引用,只要它不被删除,它所有的父元素(图中红色部分)都不会被删除
1.15 如何使低版本的浏览器如IE8等支持js的新语言特性如数组的forEach方法?
  • 首先先来判断一下浏览器是否支持js的forEach,代码如下:
    if (typeof Array.prototype.forEach != 'function') {
        //不支持,此时我们需要自己定义一个类似forEach功能的函数。
    }
    
  • 如果浏览器不支持forEach,就需要写一个foreach功能的函数。具体函数体请看下面代码:
    function(callback){
          for (var i = 0; i < this.length; i++){
            callback.apply(this, [this[i], i, this]);
          }
    }
    
  • 所以,解决IE8不支持forEach的方法应该是这样的:
    if (typeof Array.prototype.forEach != 'function') {
        Array.prototype.forEach = function(callback){
          for (var i = 0; i < this.length; i++){
            callback.apply(this, [this[i], i, this]);
          }
        };
    }
    

2.网络相关知识点

2.1简单讲解一下http2的多路复用?
  • 在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:
    • 第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)。
    • 第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。
  • HTTP/2的多路复用就是为了解决上述的两个性能问题:
    • 在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
    • 多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。
2.2谈谈你对TCP三次握手和四次挥手的理解?
  • 三次握手
    • 客户端发送syn(包含req,保证输入的安全性,以及验证包的顺讯)包到服务器,等待服务器接受。
      • 支持的协议版本
      • 一个客户端生成的随机数,用于生成‘对话秘钥‘
      • 支持的加密方法,比如RSA加密方法
      • 支持的压缩方法
    • 服务器接受数据包并确认客户的syn,并发送syn+ack(确认字符)的包给客户端
      • 确认使用的加密通信的协议版本,如果浏览器与服务器支持的版本不一致,服务器关闭加密通信
      • 一个服务器生成的随机数,用于生成‘对话秘钥‘
      • 确认使用的加密方法,比如RSA加密方法
      • 服务器证书
    • 客户端确认接受服务器接受的syn+ack的包,并向服务器发送确认包ack
      • 一个随机数。该随机数用服务器公钥加密,防止被窃听
      • 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送
      • 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验
  • 四次挥手
    • 客户端发送fin信号,告诉服务器需要断开连接,等待服务器响应
    • 服务器接收到fin信号并确认,回应等待数据发送完成请求,发送ack包(失败可以重新发送)
    • 服务器数据传输完毕,发送给客户端确认信息,等待客户端响应,如响应,则服务端直接关闭
    • 客户端收到响应,并确认信息,然后再次发送ack包给服务端,并进入time_wait状态,等待2MSL(最大报文生存时间)后,没有响应,则直接关闭
  • HTTPS 握手过程中,客户端如何验证证书的合法性?
    • 校验证书的颁发机构是否受客户端信任
    • 通过CRL和OCSP的方式验证证书时候被吊销
    • 对比系统时间,看证书是否到期
    • 通过校验对方是否存在证书的私钥,判断证书的网站域名是否与证书颁发的域名一致
2.3介绍 HTTPS 握手过程
  • http是明文传输的数据,存在着敏感数据被截获等风险。于是https应运而生,简单来说https=http+ssl/tls,即加密的http。其中ssl(Secure Socket Layer,安全套接字层)和tls(Transport Layer Security)都是用来加密的协议,可简单理解为加密算法不同。其他的加密概念有:对称加密、非对称加密(公钥、私钥)、数字证书、摘要、数字签名。
  • https的握手过程:
    • 客户端提交https请求
    • 服务器响应客户并把服务器公钥发给客户端
    • 客户端验证公钥的有效性
    • 有效后客户端会生成一个会话密钥(一个随机数)
    • 用服务器公钥加密这个会话密钥后发送给服务器
    • 服务器收到公钥加密的密钥后用私钥解密获取会话密钥
    • 客户端与服务器利用会话密钥对传输数据进行对称加密通信
      在这里插入图片描述
  • 客户端如何检验公钥是不是合法?
    • 拿着服务器发来的公钥再发请求去CA那做检验吗?其实不是,客户端需要预置CA签发的根证书,这个根证书中保存了CA的公钥。
    • 而且在之前的第2步中,服务器发的并不是服务器公钥,而是由CA签发的服务器证书,这个证书包括了两部分:用CA私钥对服务器公钥以及其他网站信息加密后得到的的密文+对服务器公钥hash后的摘要。
    • 服务器将证书发给客户端以后,客户端从CA根证书中获取CA公钥,对服务器证书的密文进行解密,得到服务器公钥(当然还有网站的其他信息,这里先忽略),然后再hash一次公钥,比较得到的结果和证书携带的摘要是否一致。
    • 过程如下图所示:
      在这里插入图片描述
  • 服务器证书的摘要
    • 其实,服务器证书携带服务器公钥等信息(还包括域名、域名持有者、证书失效时间、证书签发时间等信息)的明文和这些信息hash后的摘要都是安全的。
    • 假如服务器证书携带的是服务器公钥等信息的明文,客户端在解密密文得到服务器公钥等信息后,直接和证书携带的明文信息比较,也能判断明文信息是否被修改过。
    • 因此,证书携带摘要而不是公钥等信息的明文目的在于:减少发送的数据量,从而减少数据发送时间。

数字证书、摘要、数字签名

2.4cookie 和 token 都存放在 header 中,为什么不会劫持 token?
  • cookie
    • 攻击者通过xss拿到用户的cookie然后就可以伪造cookie了
    • 通过csrf在同个浏览器下面通过浏览器会自动带上cookie的特性
    • 在通过 用户网站-攻击者网站-攻击者请求用户网站的方式 浏览器会自动带上cookie
  • token
    • 不会被浏览器带上问题2解决
    • token是放在jwt里面下发给客户端的而且不一定存储在哪里不能通过document.cookie直接拿到,通过jwt+ip的方式 可以防止被劫持即使被劫持也是无效的jwt
  • cookie:登陆后后端生成一个sessionid放在cookie中返回给客户端,并且服务端一直记录着这个sessionid,客户端以后每次请求都会带上这个sessionid,服务端通过这个sessionid来验证身份之类的操作。所以别人拿到了cookie拿到了sessionid后,就可以完全替代你。
  • token:登陆后后端不返回一个token给客户端,客户端将这个token存储起来,然后每次客户端请求都需要开发者手动将token放在header中带过去,服务端每次只需要对这个token进行验证就能使用token中的信息来进行下一步操作了。
  • xss:用户通过各种方式将恶意代码注入到其他用户的页面中。就可以通过脚本获取信息,发起请求,之类的操作。
  • csrf:跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。csrf并不能够拿到用户的任何信息,它只是欺骗用户浏览器,让其以用户的名义进行操作。
  • csrf例子:假如一家银行用以运行转账操作的URL地址如下:
    • http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName那么一个恶意攻击者可以在另一个网站上放置如下代码: 如果有账户名为Alice的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失1000资金。
  • 上面的两种攻击方式,如果被xss攻击了,不管是token还是cookie,都能被拿到,所以对于xss攻击来说,cookie和token没有什么区别。但是对于csrf来说就有区别了。
  • 以上面的csrf攻击为例:
    • cookie:用户点击了链接,cookie未失效,导致发起请求后后端以为是用户正常操作,于是进行扣款操作。
    • token:用户点击链接,由于浏览器不会自动带上token,所以即使发了请求,后端的token验证不会通过,所以不会进行扣款操作。
2.5HTTPS 握手过程中,客户端如何验证证书的合法性
  • 简单来说是验证两个问题:
    • 证书是否是信任的有效证书。所谓信任:浏览器内置了信任的根证书,就是看看web服务器的证书是不是这些信任根发的或者信任根的二级证书机构颁发的。所谓有效,就是看看web服务器证书是否在有效期,是否被吊销了。
    • 对方是不是上述证书的合法持有者。简单来说证明对方是否持有证书的对应私钥。验证方法两种,一种是对方签个名,我用证书验证签名;另外一种是用证书做个信封,看对方是否能解开。以上的所有验证,除了验证证书是否吊销需要和CA关联,其他都可以自己完成。验证正式是否吊销可以采用黑名单方式或者OCSP方式。黑名单就是定期从CA下载一个名单列表,里面有吊销的证书序列号,自己在本地比对一下就行。优点是效率高。缺点是不实时。OCSP是实时连接CA去验证,优点是实时,缺点是效率不高。
2.6介绍下如何实现 token 加密
  • jwt(JSON WEB Token)举例
    • 需要一个secret(随机数)
    • 后端利用secret和加密算法(如:HMAC-SHA256)对payload(如账号密码)生成一个字符串(token),返回前端
    • 前端每次request在header中带上token
    • 后端用同样的算法解密
2.7介绍下 HTTPS 中间人攻击
  • 背景知识
    • HTTPS和HTTP的区别
    • 中间人攻击(sslstrip和sslsplit的原理)
    • 端口转发
    • 数据重定向的方法(手动修改网关、arp欺骗)
  • 原理介绍
    • 1.HTTPS和HTTP
      • HTTPS是在HTTP应用层基础上使用SSL(完全套接层)作为子层,SSL使用数据加密技术确保数据在网络上传输而不会被截取及窃听。
    • 2.中间人攻击
      • SSLStrip (降级攻击)的工作原理及步骤
        • 先进行中间人攻击来拦截 HTTP 流量。
        • 将出现的 HTTPS 链接全部替换为 HTTP,同时记下所有改变的链接。
        • 使用 HTTP 与受害者机器连接。
        • 同时与合法的服务器建立 HTTPS。
        • 受害者与合法服务器之间的全部通信经过了代理转发。
        • 其中,出现的图标被替换成为用户熟悉的“小黄锁”图标,以建立信任。
        • 这样,中间人攻击就成功骗取了密码、账号等信息,而受害者一无所知。
        • 总而言之,SSLStrip是一种降级攻击。
      • SSLSplit(解密攻击)工作原理
        • 工具的主要原理是以中间人的身份将证书插入到客户端和服务器中间,从而截断客户端和服务器之间的数据。
          之前我们大多数做的都是针对于80端口的欺骗(http),也就是说,只要是超越了80端口我们就会有点棘手:比如常用的443端口(https),比如465(smtps)和587端口,这些都是通过SSL加密进行数据传输的,简单的80端口监听肯定是什么都拿不到的。这个时候,就体现出SSL证书劫持的作用了。
        • 总而言之,SSLSplit是一种伪造证书攻击。
    • 3.端口转发
      在这里插入图片描述
      • 数据包都是有原地址和目标地址的,NAT(network address translation,网络地址转换)就是要对数据包的原地址或者目标地址(也可以修改端口的)进行修改的技术。为什么我们要修改ip地址呢?是这样的互联网中只能传送公网地址的数据包,私有地址的数据包是无法传送的。这样你想下,你每天在wifi环境下看视频浏览网站的时候你的ip是什么(私有地址,你手机、pad、电脑发出来的所有数据包原地址都是私有地址。怎么在互联网上传送)。为了能让你的数据包在能在互联网上传送,必须给你一个公网ip才行。所以当你上互联网的时候,路由器会帮你把所有的数据包的原地址转换成它的wlan口的ip地址(这个就是公网ip,一般就是ADSL拨号获取的ip)。这个转换的技术就是NAT。当你所访问的服务器给你回应数据包时,路由器会把所有数据包目标地址,由它的wlan口的ip地址,改回你内网的ip地址。这样你才能上互联网。所以你每天都在使用NAT技术。
    • 4.数据重定向的方法
      • 如何重定向到攻击者电脑上成为靶机和服务器的中间人,其实有很多种方式,比如:
        • arp攻击(伪装网关)
        • DNS劫持(伪装服务器)
        • wifi钓鱼(之前pxy他们组做的实验就可以利用起来)
        • 修改hosts文件(把舍友暴打一顿,然后把他的电脑里的hosts文件改掉)
        • 修改默认网关(把舍友暴打一顿,然后把他的电脑里的默认网关改成自己的ip)
2.8介绍下前端加密的常见场景和方法
  • 首先,加密的目的,简而言之就是将明文转换为密文、甚至转换为其他的东西,用来隐藏明文内容本身,防止其他人直接获取到敏感明文信息、或者提高其他人获取到明文信息的难度。通常我们提到加密会想到密码加密、HTTPS 等关键词,这里从场景和方法分别提一些我的个人见解。
  • 场景-密码传输
    • 前端密码传输过程中如果不加密,在日志中就可以拿到用户的明文密码,对用户安全不太负责。
    • 这种加密其实相对比较简单,可以使用 PlanA-前端加密、后端解密后计算密码字符串的MD5/MD6存入数据库;也可以 PlanB-直接前端使用一种稳定算法加密成唯一值、后端直接将加密结果进行MD5/MD6,全程密码明文不出现在程序中。
    • PlanA:使用 Base64 / Unicode+1 等方式加密成非明文,后端解开之后再存它的 MD5/MD6 。
    • PlanB:直接使用 MD5/MD6 之类的方式取 Hash ,让后端存 Hash 的 Hash 。
  • 场景-数据包加密
    • 应该大家有遇到过:打开一个正经网站,网站底下蹦出个不正经广告——比如X通的流量浮层,X信的插入式广告……(我没有针对谁)但是这几年,我们会发现这种广告逐渐变少了,其原因就是大家都开始采用 HTTPS 了。被人插入这种广告的方法其实很好理解:你的网页数据包被抓取->在数据包到达你手机之前被篡改->你得到了带网页广告的数据包->渲染到你手机屏幕。 而 HTTPS 进行了包加密,就解决了这个问题。严格来说我认为从手段上来看,它不算是一种前端加密场景;但是从解决问题的角度来看,这确实是前端需要知道的事情。
    • Plan:全面采用 HTTPS
  • 场景-展示成果加密
    • 经常有人开发网页爬虫爬取大家辛辛苦苦一点一点发布的数据成果,有些会影响你的竞争力,有些会降低你的知名度,甚至有些出于恶意爬取你的公开数据后进行全量公开……比如有些食谱网站被爬掉所有食谱,站点被克隆;有些求职网站被爬掉所有职位,被拿去卖信息;甚至有些小说漫画网站赖以生存的内容也很容易被爬取。

    • Plan:将文本内容进行展示层加密,利用字体的引用特点,把拿给爬虫的数据变成“乱码”。举个栗子:正常来讲,当我们拥有一串数字“12345”并将其放在网站页面上的时候,其实网站页面上显示的并不是简单的数字,而是数字对应的字体的“12345”。这时我们打乱一下字体中图形和字码的对应关系,比如我们搞成这样:

      图形:1 2 3 4 5
      字码:2 3 1 5 4
      
    • 这时,如果你想让用户看到“12345”,你在页面中渲染的数字就应该是“23154”。这种手段也可以算作一种加密。

2.9简单谈一下 cookie ?
  • cookie 是服务器提供的一种用于维护会话状态信息的数据,通过服务器发送到浏览器,浏览器保存在本地,当下一次有同源的请求时,将保存的 cookie 值添加到请求头部,发送给服务端。这可以用来实现记录用户登录状态等功能。cookie 一般可以存储 4k 大小的数据,并且只能够被同源的网页所共享访问。
  • 服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。一条cookie 包括了5个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的时间,domain 是域名、path是路径,domain 和 path 一起限制了 cookie 能够被哪些 url 访问。secure 规定了 cookie 只能在确保安全的情况下传输,HttpOnly 规定了这个 cookie 只能被服务器访问,不能使用 js 脚本访问。
  • 在发生 xhr(XMLHttpRequest) 的跨域请求的时候,即使是同源下的 cookie,也不会被自动添加到请求头部,除非显示地规定。
2.10什么是 XSS 攻击?如何防范 XSS 攻击?
  • XSS攻击全称跨站脚本攻击,是一种在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。
  • 常见的 XSS 攻击有三种:反射型XSS攻击、DOM-based 型XXS攻击以及存储型XSS攻击。
    • 反射型XSS攻击反射型 XSS 一般是攻击者通过特定手法(如电子邮件),诱使用户去访问一个包含恶意代码的 URL,当受害者点击这些专门设计的链接的时候,恶意代码会直接在受害者主机上的浏览器执行。反射型XSS通常出现在网站的搜索栏、用户登录口等地方,常用来窃取客户端 Cookies 或进行钓鱼欺骗。
    • 存储型XSS攻击也叫持久型XSS,主要将XSS代码提交存储在服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交XSS代码。当目标用户访问该页面获取数据时,XSS代码会从服务器解析之后加载出来,返回到浏览器做正常的HTML和JS解析执行,XSS攻击就发生了。存储型 XSS 一般出现在网站留言、评论、博客日志等交互处,恶意脚本存储到客户端或者服务端的数据库中。
    • DOM-based 型XSS攻击基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞。
  • 防御XSS攻击的方法有:
    • 对输入内容的特定字符进行编码,例如表示 html标记的 < > 等符号。
    • 对重要的 cookie设置 httpOnly, 防止客户端通过document.cookie读取 cookie,此 HTTP头由服务端设置。
    • 将不可信的值输出 URL参数之前,进行 URLEncode操作,而对于从 URL参数中获取值一定要进行格式检测(比如你需要的时URL,就判读是否满足URL格式)。
    • 不要使用 Eval来解析并运行不确定的数据或代码,对于 JSON解析请使用 JSON.parse() 方法。
    • 后端接口也应该要做到关键字符过滤的问题。
2.11什么是 CSP?
  • CSP(Content Security Policy)指的是内容安全策略 ,是一个附加的安全层,用于帮助检测和缓解某些类型的攻击,包括跨站脚本攻击 (XSS) 和数据注入等攻击。
  • 这些攻击可用于实现从数据窃取到网站破坏或作为恶意软件分发版本等用途,为了缓解很大一部分潜在的跨站脚本问题,浏览器的扩展程序系统引入了内容安全策略(CSP)的一般概念。
  • 这将引入一些相当严格的策略,会使扩展程序在默认情况下更加安全,开发者可以创建并强制应用一些规则,管理网站允许加载的内容。
  • 简单来说,就是我们能够规定,我们的网站只接受我们指定的请求资源
  • CSP意义
    • 防XSS等攻击的利器
    • CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增强了网页的安全性。攻击者即使发现了漏洞,也没法注入脚本,除非还控制了一台列入了白名单的可信主机
  • CSP应用
    • CSP 可以由两种方式指定:HTTP Header 和 HTML
    • HTTP 是在 HTTP 由增加Header的Content-Security-Policy字段来指定,如下
      在这里插入图片描述
    • 第二种则是由HTML的Meta标签指定,不过这种方式存在缺陷,每个页面都需要添加,而且不能对限制的域名进行上报
      <meta http-equiv="Content-Security-Policy" content="default-src 'self' *.xx.com *.xx.cn 'unsafe-inline' 'unsafe-eval';">
      
2.12什么是 CSRF 攻击?如何防范 CSRF 攻击?
  • CSRF(Cross-site request forgery), 中文名称:跨站请求伪造。攻击者盗用了你的身份,以你的名义发送恶意请求。
  • CSRF能做的事情包括:以你的名义发送邮件,发消息,盗取你的账号,在受害者不知情的情况下,以受害者名义伪造请求发送给受攻击站点,从而在受害者并未授权的情况下执行受害者权限下的各种操作。
  • CSRF攻击的原理
    • 正常网站A,存在CSRF漏洞;恶意网站B含有攻击性代码,用来对网站A进行攻击。
    • 正常网站A,有两个用户user01(受害者)和user02(攻击者)。
    • user02(攻击者)清楚的了解网站A,并创建了具有攻击性的网站B(钓鱼网站)。
    • user01(受害者)登录了网站A后,在自身的session未失效的情况下,访问了恶意网站B.
  • CSRF攻击的过程
    • 用户user01通过浏览器访问正常网站A,输入用户名和密码请求登录验证
    • 登录验证通过后,网站A保存user01的session,并将对应的cookie返回给user01的浏览器。这样user01就可以在网站A执行自身权限下的各种请求(操作),比如取钱,发表文章,发表评论等
    • user01在未退出网站A的时候,在同一浏览器,点击访问了恶意网站B(钓鱼网站),此时user02拿到user01的认证信息或者登录状态
    • 网站B是user02创建的,user02清楚的知道网站A的工作模式,网站B通过攻击性代码访问网站A(携带的是user01的cookie),执行某些并非user01授意的操作
    • 网站A并不知道这个恶意请求是从网站B发出的,因此,就会根据user01在网站A中具备的相关权限,执行权限下的各种操作。这样,就在user01不知情的情况下,user02假冒了user01,执行了具备user01用户身份才可以执行的操作
  • CSRF攻击实例
    • 假设,现在有一个受害者Bob,在网站http://bank.expample/有一大笔存款。 Bob通过银行的网站发送请求http://bank.example/withdraw?account=Bob&amount=100000&to=Bob2。Bob将100000的存款转账到Bob2的账户下,通常情况下,该请求发送到银行网站后,服务器会先验证该请求是否来自一个合法的session,该session的用户Bob已经成功登录。黑客Hack自己在该银行也有自己的账户,他知道银行转账操作的URL。Hack可以自己发送一个请求给银行:http://bank.example/withdraw?account=Bob&amount=100000&to=Hack,但是这个请求来自Hack,并非Bob,他不能通过安全验证,因此该请求不会起作用。这时,Hack香到使用CSRF的攻击方式,他先自己做一个网站B,在网站B中放入代码:src=“http://bank.example/withdraw?account=Bob&amount=100000&to=Hack”,并通过广告等方式诱使Bob访问他的网站。当Bob访问网站B的时候,上述URL就会从Bob的浏览器发向银行,并且这个请求会附带Bob浏览器的cookie一起发现银行服务器。当然,大多数情况下,该请求会失败(session有有效时间),因为银行网站需要要求Bob的认证信息。但是如果Bob当时恰巧刚访问银行网站后不久,他的浏览器与银行网站的session尚未过期(比如Bob在一个窗口还未退出银行网站),而浏览器中的cookie就含有Bob的认证信息,银行网站的对应session数据还在。这时悲剧就发生了,这个URL会得到银行服务器的响应,钱将从Bob的账号转移到Hack的账号,而Bob并不知情。等事后Bob发现账户钱变少了,去银行查询流水,却发现是他自己转移账户的钱,没有任何被攻击的痕迹(我搞我自己?人类迷惑行为)。这个示例中,银行网站错误的认为,这个转账时Bob本人执行的。
  • CSRF攻击的对象
    • 从以上的例子可知,CSRF攻击是黑客借助受害者的cookie伪造请求,骗取服务器的信任。黑客所能做的就是给服务器发送伪造请求,改变请求时的参数。所以我们要保护的对象是那些可以直接产生数据改变的服务,而对于读取数据的服务。则不需要进行CSRF的保护。比如银行系统中转账的请求会直接改变账户的金额,会遭到CSRF攻击,需要保护,而查询余额是对金额的读取操作,不会改变数据,无需保护。
  • CSRF漏洞检测
    • 检测CSRF漏洞是一项比较繁琐的工作,最简单的一个方法就是抓取一个正常请求的数据包,去掉Referer字段后再重新提交,如果该提交还有效,那么基本上可以确定存在CSRF漏洞。随着对CSRF漏洞研究的不断深入,出现了一些针对CSRF漏洞进行检测的工具,如CSRFTester,CSRE Request Builder等。
    • CSRFTester工具的测试原理大概是这样的,使用代理抓取我们在浏览器中访问过的所有的连接以及所有的表单等信息,通过在CSRFTester中修改相应的表单等信息,重新提交,相当于一次伪造客户端请求,如果修测试的请求成功被网站服务器接受,则说明存在CSRF漏洞,当然此款工具也可以被用来进行CSRF攻击。
  • 防范CSRF攻击的几种策略
    • 防范原理:防范CSRF攻击,其实本质上就是要求网站能够识别出哪些请求是非正常用户主动发起的。这就要求我们在请求中嵌入一些额外的授权数据,让网站服务器能够区分出这些未授权的请求。比如在请求参数中加一个字段,这个字段的值从登录用户的Cookie或者页面中获取(这个值需要是随机的,无规律可循)。攻击者伪造请求的时候无法获取页面中与登录用户有关的一个随机值或者cookie中的内容的。因此就可以避免这种攻击
    • 目前防御CSRF攻击主要有以下几种策略
      • 验证HTTP Referer字段
      • 使用验证码(关键页面加上验证码验证,这种方法对用户不友好,不推荐)
      • 在请求地址中加入token并验证
      • 在HTTP头中自定义属性并验证
  • 验证HTTP Referer字段
    • HTTP头中的Referer字段记录该请求的来源地址。比如访问http://bank.example/withdraw?account=Bob&amount=100000&to=Hack,用户必须先登录http://bank.example,然后通过该网站页面的转账按钮来触发转账事件。这时,该转账请求的Referer值就会是转账按钮所在页面的URL,通常以bank.example域名开头的地址。而如果黑客要对银行网站实施CSRF攻击,他只能在自己的网站构造请求,当用户通过黑客的网站发送请求到银行网站时,该请求的Referer是指向黑客自己的网站。因此要防御CSRF攻击,银行只需要对转账请求验证其Referer值,如果以bank.example开头的域名,则说明该请求是来自银行网站自己的请求,是合法的,如果Referer是其他网站的话,则有可能是黑客的CSRF攻击,拒绝该请求。
    • 优点: 简单易行,只需要在最后给所有敏感的请求统一增加一个拦截器来检查Referer的值就可以。特别对于当前现有的系统,不需要改变当前系统已有代码,没有风险,简单便捷
    • 缺点: 这种方法并非万无一失 。首先,Referer的值是由浏览器提供的,但是每个浏览器对不Referer的具体实现会有差别,并不能保证浏览器自身没有安全漏洞。验证Referer的值,把安全性都依赖于浏览器来保障,不安全。而已已有一些方法可以篡改Referer值。另外,用户会因为隐私问题,设置浏览器不允许发送Referer值,这样服务端的Referer验证就没有了意义。
  • 在请求地址中添加token并验证
    • CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户的 cookie来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue 。而对于 POST 请求,要在 form 表单加上:该方法也有一个缺点是难以保证 token 本身的安全。因为即使是 POST 请求的 token,黑客的网站也同样可以通过 Referer 的值来得到这个 token 值以发动 CSRF 攻击。这也是一些用户喜欢手动关闭浏览器 Referer 功能的原因。如果想保证 token 本身的安全,可以考虑使用动态 token,也就是每次请求都使用不同的动态 token。
  • 在 HTTP 头中自定义属性并验证
    • 这种方法也是使用 token 进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到HTTP 头中自定义的属性里。通过 XMLHttpRequest 对象,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。然而,这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 对页面局部的异步刷新。并非所有的请求都适合用 Ajax 来发起,而且通过该类请求得到的页面不能被浏览器所记录,影响前进、后退、刷新、收藏等操作,给用户带来了不便。另外,对于没有进行 CSRF 防护的旧系统来说,如果采用这种方法来进行防护,需要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,工作量无疑是巨大的。
2.13什么是点击劫持?如何防范点击劫持?
  • 最常见的点击劫持是恶意网站使用 标签把我方的一些含有重要信息类如交易的网页嵌入进去,然后把 iframe 设置透明,用定位的手段的把一些引诱用户在恶意网页上点击。这样用户不知不觉中就进行了某些不安全的操作。
  • 有两种方式可以防范
    • 使用 JS 防范: if (top.location.hostname !== self.location.hostname) { alert(“您正在访问不安全的页面,即将跳转到安全页面!”); top.location.href = self.location.href; }
    • 使用 HTTP 头防范: 通过配置 nginx 发送 X-Frame-Options 响应头,这样浏览器就会阻止嵌入网页的渲染。更详细的可以查阅 MDN 上关于 X-Frame-Options 响应头的内容。 add_header X-Frame-Options SAMEORIGIN
2.14http状态码,cookie字段,cookie一般存的是什么,session怎么存在的?
  • http协议
    • 请求组成:请求行,消息报头,请求正文
    • 请求常用的方法:
      • GET: 请求指定的页面信息,并返回实体主体;
      • POST: 请求服务器接受所指定的文档作为对所标识的URI的新的从属实体;
  • http状态及响应码
    • 1(信息类):表示接收到请求并且继续处理
      • 100:客户必须继续发出请求
      • 101:客户要求服务器根据请求转换HTTP协议版本
    • 2(响应成功):表示动作被成功接收、理解和接受
      • 200:表明该请求被成功的完成,所请求的资源发送回客户端
      • 201:提示知道新文件的URL
      • 202:接受和处理,但处理未完成
      • 203:返回信息不确定或不完整
      • 204:请求收到,但返回信息为空
      • 205:服务器完成了请求,用户代理必须复位当前已经浏览过的文件
      • 206:服务器已经完成了部分用户的GET请求
    • 3(重定向类):为了完成指定的动作,必须接受进一步的处理
      • 300:请求的资源可在多处得到
      • 301:本网页被永久性转移到另一个URL
      • 302:请求的网页被转移到一个新的地址,但客户访问仍继续通过原始URL地址,重定向,新的URL会在response中的Location中返回,浏览器将会使用新的URL发出新的Request
      • 303:建议客户访问其他URL或访问方式
      • 304:自从上次请求后,请求的网页未修改过,服务器返回此响应时,不会返回网页内容,代表上次的文档已经被缓存了,还可以继续使用
      • 305:请求的资源必须从服务器置顶的地址得到
      • 306:前一版本HTTP中使用的代码,现行版本中不再使用
      • 307:申明请求的资源临时性删除
    • 4(客户端错误):请求包含错误语法或不能正确执行
      • 400:客户端请求有语法错误,不能被服务器所理解
      • 401:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
      • 402:保留有效ChargeTo头响应
      • 403:禁止访问,服务器收到请求,但是拒绝提供服务
      • 404:一个404错误表明可连接服务器,但服务器无法取得所请求的网页,请求资源不存在,eg:输入了错误的URL
      • 405:用户在Request-Line字段定义的方法不允许
      • 406:根据用户发送的Accept拖,请求资源不可访问
      • 407:类似401,用户首先在代理服务器上得到授权
      • 408:客户端没有在用户指定的时间内完成请求
      • 409:对当前资源状态,请求不能完成
      • 410:服务器上不再有此资源且无进一步的参考地址
      • 411:服务器拒绝用户定义的Content-Length属性请求
      • 412:一个或多个请求头字段在当前请求中错误
      • 413:请求的资源大于服务器允许的大小
      • 414:请求的资源URL长于服务器允许的长度
      • 415:请求资源不支持请求项目格式
      • 416:请求中包含Range请求头字段,在当前请求资源范围内没有range指示值,请求也不包含If-Range请求头字段
      • 417:服务器不满足请求Expect头字段指定的期望值,如果是代理服务器,可能是下一级服务器不能满足请求长
    • 5(服务端错误类):服务器不能正确执行一个正确的请求
      • 500:服务器遇到错误,无法完成请求
      • 501:未实现
      • 502:网关错误
      • 503:由于超载或停机维护,服务器目前无法使用,一段时间后可能恢复正常
  • 通过Cookies保存状态信息
    • 通过Cookies,服务器就可以清楚的知道请求2和请求1来自同一个客户端。
      在这里插入图片描述
  • 通过Session保存状态信息
    • Session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。
    • 当程序需要为某个客户端请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个session标识,称为session id,如果已包含一个session id则说明以前已经为此客户创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含session id,则为此客户端创建一个session并且声称一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个session id将被在本次响应中返回给客户端保存。
    • session id:一串银行卡号
    • cookie还会保存哪家银行卡,姓名等等
2.15http请求方式有哪些?
  • HTTP的常用方法
    • GET:通过请求URI得到资源
    • POST:用于添加新的内容
    • PUT:用于修改某个内容
    • DELETE:删除某个内容
    • CONNECT:用于代理进行传输,如使用SSL
    • OPTIONS:询问可以执行哪些方法
    • PATCH:部分文档更改
    • PROPFIND:查看属性
    • PROPPATCH:设置属性
    • MKCOL:创建集合(文件夹)
    • COPY:拷贝
    • MOVE:移动
    • LOCK:加锁
    • UNLOCK:解锁
    • TRACE:用于远程诊断服务器
    • HEAD:类似于GET,但是不返回body信息,用于检查对象是否存在,以及得到对象的元数据
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值