#target {
will-change: transform;
7.为什么JS是单线程的?
这主要与JS的用途有关,JS作为浏览器的脚本语言,最初主要是实现用户与浏览器的交互,以及操作DOM。这就决定了它只能是单线程,否则会带来许多复杂的同步问题。
举个例子🌰: 如果JS是多线程的,其中一个线程要修改一个DOM元素,另外一个线程想要删除这个DOM元素,这时候浏览器就不知道该听谁的。所以为了避免复杂性,从一诞生,JavaScript就被设计成单线程。
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质
8.CSS加载会阻塞DOM吗?
CSS
不会阻塞DOM
的解析,但会阻塞DOM
的渲染
CSS
会阻塞JS
执行,但不会阻塞JS
文件的下载
CSSOM的作用
第一个是提供给JavaScript操作样式表的能力
第二个是为布局树的合成提供基础的样式信息
这个CSSOM体现在DOM中就是document.styleSheets
由之前讲到的浏览器渲染流程我们可以看出:
DOM和CSSOM通常是并行构建的,所以CSS加载不会阻塞DOM的解析
render树是依赖DOM树和CSSOM树的,所以它必须等到两者都加载完毕才能开始构建渲染,所以CSS加载会阻塞DOM的渲染
由于JavaScript是可以操作DOM与CSS的,如果在修改这些元素属性同时渲染界面(即JavaScript线程与UI线程同时进行),那么渲染线程前后获得的元素可能就不一致了。所以为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JavaScript线程为互斥的关系
JS需要等待CSS的下载,这是为什么呢?(CSS阻塞DOM执行)
如果JS
脚本的内容是获取元素的样式,那它就必然依赖CSS
。因为浏览器无法感知JS
内部到底想干什么,为避免样式获取,就只好等前面所有的样式下载完毕再执行JS
。但JS文件与CSS文件下载是并行的,CSS文件会在后面的JS文件执行前先加载执行完毕,所以CSS会阻塞后面JS的执行
避免白屏,提高CSS的加载速度
使用CDN(CDN会根据你的网络状况,挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)
对CSS进行压缩
合理使用缓存
减少http请求数,合并CSS文件
9.JS会阻塞页面吗?
JS会阻塞DOM的解析,因此也就会阻塞页面的加载
这也是为什么我们常说要把JS文件放在最下面的原因
由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。
因此为了防止渲染出现不可预期的结果,浏览器设置 「GUI 渲染线程与 JavaScript 引擎为互斥」的关系。
当 JavaScript 引擎执行时 GUI 线程会被挂起,GUI 更新会被保存在一个队列中等到引擎线程空闲时立即被执行。
当浏览器在执行 JavaScript 程序的时候,GUI 渲染线程会被保存在一个队列中,直到 JS 程序执行完成,才会接着执行。
因此如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。
10.defer和async的区别?
两者都是异步去加载外部JS文件,不会阻塞DOM解析
Async是在外部JS加载完成后,浏览器空闲时,Load事件触发前执行,标记为async的脚本并不保证按照指定他们的先后顺序执行,该属性对于内联脚本无作用 (即没有「src」属性的脚本)。
defer是在JS加载完成后,整个文档解析完成后,触发 DOMContentLoaded
事件前执行,如果缺少 src
属性(即内嵌脚本),该属性不应被使用,因为这种情况下它不起作用
11.浏览器的垃圾回收机制
垃圾回收是一种自动的内存管理机制。当计算机上的动态内存不再需要时,就应该予以释放。
需要注意的是,自动的意思是浏览器可以自动帮助我们回收内存垃圾,但并不代表我们不用关心内存管理,如果操作不当,JavaScript中仍然会出现内存溢出的情况,造成系统崩溃。
由于字符串,数组,对象等都没有固定大小,因此需要当它们大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串,数组或对象时,解释器都必须分配内存来存储那个实体。
JavaScript解释器可以检测到何时程序不在使用一个对象了,当它确定这个对象是无用的时候,他就知道不再需要这个对象了,就可以把它占用的内存释放掉了。
浏览器通常采用的垃圾回收有两种方法:标记清除,引用计数。
这是JavaScript中最常用的垃圾回收方式
从2012年起,所有现代浏览器都使用了标记清除的垃圾回收方法,除了低版本IE还是采用的引用计数法。
那么什么叫标记清除呢?
JavaScript中有一个全局对象,定期的,垃圾回收器将从这个全局对象开始,找出所有从这个全局对象开始引用的对象,再找这些对象引用的对象...对这些活跃的对象标记,这是标记阶段。清楚阶段就是清楚那些没有被标记的对象。
标记清除有一个问题,就是在清除之后,内存空间是不连续的,即出现了内存碎片。如果后面需要一个比较大的连续的内存空间,那将不能满足要求。而标记整理 方法可以有效德地解决这个问题。
在标记的过程中,引入了概念:三色标记法,三色为:
白:未被标记的对象,即不可达对象(没有扫描到的对象),可回收
灰:已被标记的对象(可达对象),但是对象还没有被扫描完,不可回收
黑:已被扫描完(可达对象),不可回收
标记整理:
标记阶段与标记清除法没什么区别,只是标记结束后,标记整理法会将存活的对象向内存的一边移动,最后清理掉边界内存。
引用计数的含义是跟踪记录每个值被引用的次数。当一个变量A被赋值时,这个值的引用次数就是1,当变量A重新赋值后,则之前那个值的引用次数就减1。当引用次数变成0时,则说明没有办法再访问这个值了,所以就可以清除这个值占用的内存了。
大多数浏览器已经放弃了这种回收方式
为避免内存泄漏,一旦数据不再使用,最好通过将其值设为null
来释放其引用,这个方法叫做接触引用
哪些情况会造成内存泄漏?如何避免?
以 Vue 为例,通常有这些情况:
监听在 window/body
等事件没有解绑
绑在 EventBus
的事件没有解绑
Vuex
的 $store
,watch
了之后没有 unwatch
使用第三方库创建,没有调用正确的销毁函数
解决办法:beforeDestroy
中及时销毁
绑定了 DOM/BOM
对象中的事件 addEventListener
,removeEventListener
。
观察者模式 $on
,$off
处理。
如果组件中使用了定时器,应销毁处理。
如果在 mounted/created
钩子中使用了第三方库初始化,对应的销毁。
使用弱引用 weakMap
、weakSet
。
浏览器中不同类型变量的内存都是何时释放的?
在没有引用之后,通过 V8 自动回收。
如果处于闭包的情况下,要等闭包没有引用才会被 V8 回收。
非闭包的情况下,等待 V8 的新生代切换的时候回收。
12.说一说浏览器的缓存机制?
认识浏览器缓存
当浏览器请求一个网站时,会加载各种资源,对于一些不经常变动的资源,浏览器会将他们保存在本地内存中,下次访问时直接加载这些资源,提高访问速度。
如何知道资源是请求的服务器还是读取的缓存呢?
看上面这张图,有些资源的size值是大小,有些是from disk cache
,有些是from memory cache
,显示大小的是请求的服务器资源,而显示后面两种的则是读取的缓存。
disk cache: 就是将资源存储在磁盘中,等待下次访问时不需重新下载,直接从磁盘中读取,它的直接操作对象为CurlCacheManager
。(效率比内存缓存慢,但存储容量大,存储时间长)
memory cache: 就是将资源缓存到内存中,等待下次访问时不需重新下载,直接从内存中读取。(从效率上看它是最快的,从存活时间来看,它是最短的。)
浏览器在向服务器请求资源时,首先判断是否命中强缓存,没命中再判断是否命中协商缓存
浏览器在加载资源时,会先根据本地缓存资源的header
中判断是否命中强缓存,如果命中则直接使用缓存中的资源,不会再向服务器发送请求。(这里的header中的信息指的是 expires
和 cache-control
)
Expires
该字段是 http1.0 时的规范,它的值为一个绝对时间的 GMT 格式的时间字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。所以这种方式很快在后来的HTTP1.1版本中被抛弃了。
Cache-Control
Cache-Control 是 http1.1 时出现的 header 信息,主要是利用该字段的 max-age 值来进行判断,它是一个相对时间,例如 Cache-Control:max-age=3600
,代表着资源的有效期是 3600 秒。cache-control 除了该字段外,还有下面几个比较常用的设置值:
no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
no-store:禁止使用缓存,每一次都要重新请求数据。
public:可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。
private:只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。
Cache-Control 与 Expires 可以在服务端配置同时启用,同时启用的时候 Cache-Control 优先级高。
当强缓存没命中时,浏览器会发送一个请求到服务器,服务器根据 header
中的信息来判断是否命中协商缓存。如果命中,则返回304 ,告诉浏览器资源未更新,可以使用本地缓存。(这里的header信息指的是Last-Modify/If-Modify-Since
和 ETag/If-None-Match
)
Last-Modify/If-Modify-Since
浏览器第一次请求一个资源的时候,服务器返回的 header 中会加上 Last-Modify,Last-modify 是一个时间标识该资源的最后修改时间。
当浏览器再次请求该资源时,request 的请求头中会包含 If-Modify-Since,该值为缓存之前返回的 Last-Modify。服务器收到 If-Modify-Since 后,根据资源的最后修改时间判断是否命中缓存。
如果命中缓存,则返回 304,并且不会返回资源内容,并且不会返回 Last-Modify。
短时间内资源发生了改变,Last-Modified 并不会发生变化。
周期性变化。如果这个资源在一个周期内修改回原来的样子了,我们认为是可以使用缓存的,但是 Last-Modified 可不这样认为,因此便有了 ETag。
ETag/If-None-Match
与 Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一个校验码。ETag 可以保证每一个资源是唯一的,资源变化都会导致 ETag 变化。服务器根据浏览器上送的 If-None-Match 值来判断是否命中缓存。
与 Last-Modified 不一样的是,当服务器返回 304 Not Modified 的响应时,由于 ETag 重新生成过,response header 中还会把这个 ETag 返回,即使这个 ETag 跟之前的没有变化。
Last-Modified 与 ETag 是可以一起使用的,服务器会优先验证 ETag,一致的情况下,才会继续比对 Last-Modified,最后才决定是否返回 304。
当浏览器访问一个已经访问过的资源是,它的步骤是:
1.先看是否命中强缓存,命中🎯的话直接使用缓存
2.没命中强缓存,则会发送请求到服务器看是否命中🎯协商缓存
3.如果命中了协商缓存,服务器会返回304告诉浏览器可以使用本地缓存
4.没命中协商缓存,则服务器会返回新的资源给浏览器
13.什么是浏览器的同源策略,以及跨域?
同源策略是浏览器的一种自我保护行为。所谓的同源指的是:协议,域名,端口均要相同
浏览器中大部分内容都是受同源策略限制的,但是以下三个标签不受限制:
<img src="..." />
<link href="..." />
<script src="..."></script>
跨域指的是浏览器不能执行其它域名下的脚本。它是由浏览器的同源策略限制的。
你可能会想跨域请求到底有没有发送到服务器?
事实上,跨域请求时能够发送到服务器的,并且服务器也能过接受的请求并正常返回结果,只是结果被浏览器拦截了。
跨域解决方案(列出几个常用的)
JSONP
它主要是利用script标签不受浏览器同源策略的限制,可以拿到从其他源传输过来的数据,需要服务端支持。
兼容性比较好,可用于解决主流浏览器的跨域数据访问的问题。缺点就是仅支持get请求,具有局限性,不安全,可能会受到XSS攻击。
声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。
创建一个<script>
标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。
服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是show('南玖')
。
最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。
// front
function jsonp({ url, params, callback }) {
return new Promise((resolve, reject) => {
let script = document.createElement('script')
window[callback] = function(data) {
resolve(data)
document.body.removeChild(script)
params = { ...params, callback } // wd=b&callback=show
let arrs = []
for (let key in params) {
arrs.push(`${key}=${params[key]}`)
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
jsonp({
url: 'http://localhost:3000/say',
params: { wd: 'wxgongzhonghao' },
callback: 'show'
}).then(data => {
console.log(data)
// server 借助express框架
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
let { wd, callback } = req.query
console.log(wd) // Iloveyou
console.log(callback) // show
res.end(`${callback}('关注前端南玖')`)
app.listen(3000)
上面这段代码相当于向http://localhost:3000/say?wd=wxgongzhonghao&callback=show
这个地址请求数据,然后后台返回show('关注前端南玖')
,最后会运行show()这个函数,打印出'关注前端南玖'
跨域资源共享(CORS)
CORS(Cross-Origin Resource Sharing)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。
浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求:(满足以下两个条件,就是简单请求)
1.请求方法为以下三个之一:
2.Content-Type的为以下三个之一:
text-plain
multiparty/form-data
application/x-www-form-urlencoded
复杂请求:
不是简单请求那它肯定就是复杂请求了。复杂请求的CORS请求,会在正式发起请求前,增加一次HTTP查询请求,称为预检 请求,该请求是option方法的,通过该请求来知道服务端是否允许该跨域请求。
Nginx反向代理
Nginx 反向代理的原理很简单,即所有客户端的请求都必须经过nginx处理,nginx作为代理服务器再将请求转发给后端,这样就规避了浏览器的同源策略。
14.说说什么是XSS攻击
什么是XSS?
XSS 全称是 Cross Site Scripting
,为了与css
区分开来,所以简称XSS
,中文叫作跨站脚本
XSS是指黑客往页面中注入恶意脚本,从而在用户浏览页面时利用恶意脚本对用户实施攻击的一种手段。
XSS能够做什么?
窃取Cookie
监听用户行为,比如输入账号密码后之间发给黑客服务器
在网页中生成浮窗广告
修改DOM伪造登入表单
XSS实现方式
存储型XSS攻击
反射型XSS攻击
基于DOM的XSS攻击
如何阻止XSS攻击?
对输入脚本进行过滤或转码
对用户输入的信息过滤或者转码,保证用户输入的内容不能在HTML解析的时候执行。
利用CSP
该安全策略的实现基于一个称作 Content-Security-Policy
的HTTP首部。(浏览器内容安全策略)它的核心思想就是服务器决定浏览器加载那些资源。
限制加载其他域下的资源文件,这样即使黑客插入了一个 JavaScript 文件,这个 JavaScript 文件也是无法被加载的;
禁止向第三方域提交数据,这样用户数据也不会外泄;
提供上报机制,能帮助我们及时发现 XSS 攻击。
禁止执行内联脚本和未授权的脚本;
利用 HttpOnly
由于很多 XSS 攻击都是来盗用 Cookie 的,因此还可以通过使用 HttpOnly 属性来保护我们 Cookie 的安全。这样子的话,JavaScript 便无法读取 Cookie 的值。这样也能很好的防范 XSS 攻击。
通常服务器可以将某些 Cookie 设置为 HttpOnly 标志,HttpOnly 是服务器通过 HTTP 响应头来设置的,下面是打开 Google 时,HTTP 响应头中的一段:
set-cookie: NID=189=M8l6-z41asXtm2uEwcOC5oh9djkffOMhWqQrlnCtOI; expires=Sat, 18-Apr-2020 06:52:22 GMT; path=/; domain=.google.com; HttpOnly
对于不受信任的输入,可以限制输入长度
15.说说什么是CSRF攻击?
什么是CSRF攻击?
CSRF 全称 Cross-site request forgery
,中文为跨站请求伪造 ,攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。 CSRF攻击就是黑客利用用户的登录状态,并通过第三方站点来干一些嘿嘿嘿的坏事。
几种常见的攻击类型
1.GET类型的CSRF
GET类型的CSRF非常简单,通常只需要一个HTTP请求:
<img src="http://bank.example/withdraw?amount=10000&for=hacker" >
在受害者访问含有这个img的页面后,浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker
发出一次HTTP请求。bank.example就会收到包含受害者登录信息的一次跨域请求。
2.POST类型的CSRF
这种类型的CSRF利用起来通常使用的是一个自动提交的表单,如:
<form action="http://bank.example/withdraw" method=POST>
<input type="hidden" name="account" value="xiaoming" />
<input type="hidden" name="amount" value="10000" />
<input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script>
访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作。
3.链接类型的CSRF
链接类型的CSRF并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:
<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
重磅消息!!
由于之前用户登录了信任的网站A,并且保存登录状态,只要用户主动访问上面的这个PHP页面,则表示攻击成功。
CSRF的特点
攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。
攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。
整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”。
跨站请求可以用各种方式:图片URL、超链接、CORS、Form提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。
CSRF通常是跨域的,因为外域通常更容易被攻击者掌控。但是如果本域下有容易被利用的功能,比如可以发图和链接的论坛和评论区,攻击可以直接在本域下进行,而且这种攻击更加危险。
黑客只能借助受害者的cookie
骗取服务器的信任,但是黑客并不能凭借拿到「cookie」,也看不到 「cookie」的内容。另外,对于服务器返回的结果,由于浏览器「同源策略」的限制,黑客也无法进行解析。
这就告诉我们,我们要保护的对象是那些可以直接产生数据改变的服务,而对于读取数据的服务,则不需要进行CSRF
的保护。而保护的关键,是 「在请求中放入黑客所不能伪造的信息」
既然CSRF大多来自第三方网站,那么我们就直接禁止外域(或者不受信任的域名)对我们发起请求。
那么问题来了,我们如何判断请求是否来自外域呢?
在HTTP协议中,每一个异步请求都会携带两个Header,用于标记来源域名:
Origin Header
Referer Header
这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个Header中的域名,确定请求的来源域。
在部分与CSRF有关的请求中,请求的Header中会携带Origin字段。字段内包含请求的域名(不包含path及query)。
如果Origin存在,那么直接使用Origin中的字段确认来源域名就可以。
但是Origin在以下两种情况下并不存在:
IE11同源策略: IE 11 不会在跨站CORS请求上添加Origin标头,Referer头将仍然是唯一的标识。最根本原因是因为IE 11对同源的定义和其他浏览器有不同,有两个主要的区别,可以参考MDN Same-origin_policy#IE_Exceptions
302重定向: 在302重定向之后Origin不包含在重定向的请求中,因为Origin可能会被认为是其他来源的敏感信息。对于302重定向的情况来说都是定向到新的服务器上的URL,因此浏览器不想将Origin泄漏到新的服务器上。
根据HTTP协议,在HTTP头中有一个字段叫Referer,记录了该HTTP请求的来源地址。 对于Ajax请求,图片和script等资源请求,Referer为发起请求的页面地址。对于页面跳转,Referer为打开页面历史记录的前一个页面地址。因此我们使用Referer中链接的Origin部分可以得知请求的来源域名。
这种方法并非万无一失,Referer的值是由浏览器提供的,虽然HTTP协议上有明确的要求,但是每个浏览器对于Referer的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不是很安全。在部分情况下,攻击者可以隐藏,甚至修改自己请求的Referer。
2014年,W3C的Web应用安全工作组发布了Referrer Policy草案,对浏览器该如何发送Referer做了详细的规定。截止现在新版浏览器大部分已经支持了这份草案,我们终于可以灵活地控制自己网站的Referer策略了。新版的Referrer Policy规定了五种Referer策略:No Referrer、No Referrer When Downgrade、Origin Only、Origin When Cross-origin、和 Unsafe URL。之前就存在的三种策略:never、default和always,在新标准里换了个名称。他们的对应关系如下:
属性值(新)
属性值(旧)
根据上面的表格因此需要把Referrer Policy的策略设置成same-origin,对于同源的链接和引用,会发送Referer,referer值为Host不带Path;跨域访问则不携带Referer。例如:aaa.com
引用bbb.com
的资源,不会发送Referer。
设置Referrer Policy的方法有三种:
在CSP设置
页面头部增加meta标签
a标签增加referrerpolicy属性
上面说的这些比较多,但我们可以知道一个问题:攻击者可以在自己的请求中隐藏Referer。如果攻击者将自己的请求这样填写:
<img src="http://bank.example/withdraw?amount=10000&for=hacker" referrerpolicy="no-referrer">
那么这个请求发起的攻击将不携带Referer。
另外在以下情况下Referer没有或者不可信:
1.IE6、7下使用window.location.href=url进行界面的跳转,会丢失Referer。
2.IE6、7下使用window.open,也会缺失Referer。
3.HTTPS页面跳转到HTTP页面,所有浏览器Referer都丢失。
4.点击Flash上到达另外一个网站的时候,Referer的情况就比较杂乱,不太可信。
无法确认来源域名情况
当Origin和Referer头文件不存在时该怎么办?如果Origin和Referer都不存在,建议直接进行阻止,特别是如果您没有使用随机CSRF Token(参考下方)作为第二次检查。
如何阻止外域请求
通过Header的验证,我们可以知道发起请求的来源域名,这些来源域名可能是网站本域,或者子域名,或者有授权的第三方域名,又或者来自不可信的未知域名。
我们已经知道了请求域名是否是来自不可信的域名,我们直接阻止掉这些的请求,就能防御CSRF攻击了吗?
且慢!当一个请求是页面请求(比如网站的主页),而来源是搜索引擎的链接(例如百度的搜索结果),也会被当成疑似CSRF攻击。所以在判断的时候需要过滤掉页面请求情况,通常Header符合以下情况:
Accept: text/html
Method: GET
但相应的,页面请求就暴露在了CSRF的攻击范围之中。如果你的网站中,在页面的GET请求中对当前用户做了什么操作的话,防范就失效了。
例如,下面的页面请求:
GET https://example.com/addComment?comment=XXX&dest=orderId
注:这种严格来说并不一定存在CSRF攻击的风险,但仍然有很多网站经常把主文档GET请求挂上参数来实现产品功能,但是这样做对于自身来说是存在安全风险的。
另外,前面说过,CSRF大多数情况下来自第三方域名,但并不能排除本域发起。如果攻击者有权限在本域发布评论(含链接、图片等,统称UGC),那么它可以直接在本域发起攻击,这种情况下同源策略无法达到防护的作用。
综上所述:同源验证是一个相对简单的防范方法,能够防范绝大多数的CSRF攻击。但这并不是万无一失的,对于安全性要求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护措施。
CSRF Token
前面讲到CSRF的另一个特征是,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。
而CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。
利用Cookie的SameSite属性
可以看看MDN对此的解释
SameSite
可以设置为三个值,Strict
、Lax
和None
。
在Strict
模式下,浏览器完全禁止第三方请求携带Cookie。比如请求sanyuan.com
网站只能在sanyuan.com
域名当中请求才能携带 Cookie,在其他网站请求都不能。
在Lax
模式,就宽松一点了,但是只能在 get 方法提交表单
况或者a 标签发送 get 请求
的情况下可以携带 Cookie,其他情况均不能。
在None模式下,Cookie将在所有上下文中发送,即允许跨域发送。
点赞、收藏和评论
我是前端南玖
,感谢各位的:点赞、收藏和评论,我们下期见!
欢迎👏加入前端交流群928029210
,一起交流~
-------------------------------------------
如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,文章公众号首发,关注 前端南玖 第一时间获取最新的文章~
扫描下方二维码关注公众号,回复进群,拉你进前端学习交流群(WX),这里有一群志同道合的前端小伙伴,交流技术、生活、内推、面经、摸鱼,这里都有哈,快来加入我们吧~ 回复资料,获取前端大量精选前端电子书及学习视频~