200 行代碼實現一個滑動驗證碼

  • 2019 年 10 月 4 日
  • 筆記

閱讀本文大概需要 6 分鐘。

做網絡爬蟲的同學肯定見過各種各樣的驗證碼,比較高級的有滑動、點選等樣式,看起來好像挺複雜的,但實際上它們的核心原理還是還是很清晰的,本文章大致說明下這些驗證碼的原理以及帶大家實現一個滑動驗證碼。

我之前做過 Web 相關開發,嘗試對接過 Lavavel 的極驗驗證,當時還開發了一個 Lavavel 包:https://github.com/Germey/LaravelGeetest,在開發包的過程中了解到了驗證碼的兩步校驗規則。

實際上這類驗證碼的校驗是分為兩個步驟的:

  1. 第一步就是前端的校驗。一般來說,登錄註冊頁面在點擊提交的時候都會伴隨着一個表單提交,在表單提交的時候會有 JavaScript 事件的觸發。如果加入了驗證碼,那麼在表單提交的時候會多加一個額外的驗證,判斷這個驗證碼是否已經成功完成了操作。如果沒有的話,那就直接取消表單的提交,然後順便提示說」您的驗證沒通過,請重新驗證「,諸如此類的話。所以這一步就能防範」君子「之為了。
  2. 第二步就是服務端的校驗。意思就是說表單提交之後,會有請求發送到服務器,這個請求中包含了很多數據,比如用戶名、密碼,如果對接了驗證碼的話,還會有額外的驗證碼的值,或者更複雜的加密後的 Token 值,服務器會對發過來的信息進行校驗,如果驗證通過,那麼整個請求就成功了,返回正常的響應,否則返回錯誤的響應。所以如果想要通過程序來直接構造表單提交的時候,服務端就可以做進一步的校驗,由於提交的驗證碼相關的信息都是和服務端的 Session 相關聯的,另外再加上一些 CSRF 等的校驗,所以這一步就能防範」小人「之為了。

上面就是驗證碼校驗的兩個階段,一般來說為了安全性,在開發一個網站時需要客戶端和服務端都加上校驗,這樣才能保證安全性。

本文章主要來介紹一下第一個階段,也就是前端校驗的驗證碼的實現,下面來介紹一下拖動驗證碼的具體實現。

需求

那麼前端完成一個合格的驗證碼,究竟需要做成什麼樣子呢?

  1. 首先驗證碼有個大體的雛形,既然是拖動驗證碼,那就要拖動塊和目標塊,我們需要把拖動塊拖動到目標塊上就算校驗成功。
  2. 驗證碼的一個功能就是來規避機器的自動操作,所以我們需要通過軌跡來判斷這個拖動過程是真實的人還是機器,因此我們需要記錄拖動的路徑,路徑經過計算之後可以發送到後端進行進一步的分類,比如對接深度學習模型來分類拖動軌跡是否是人。

以上就是驗證碼的兩個基本要求,所以我們這裡就來實現一下看看。

結果

這裡就先給大家看看結果吧:

拖動驗證碼示例

可以看到圖中有一個初始滑塊,有一個目標滑塊,如果把初始滑塊拖動到目標滑塊上才能校驗成功,然後下方再打印拖動的軌跡,包含它的 x、y 坐標。

有了這些內容之後,就可以放到表單裏面進行提交了,軌跡數據可以自行加密處理並校驗來判斷其是否合法。

具體實現

下面就具體講解下這個是怎麼實現的,實際上核心代碼只有 200 行,下面對整個核心流程進行說明。

既然 Vue 這麼火,那我這裡就用 Vue 來實現啦,具體的環境配置這裡就不再贅述了,需要安裝的有:

  • Node.js:https://nodejs.org/en/
  • Vue-Cli:https://cli.vuejs.org/zh/

安裝完成之後便可以使用 vue 命令了,新建個項目:

vue create drag-captcha  

然後找一張不錯的風景圖,放到 public 目錄下,後面我們會引用它。

另外這裡需要一個核心的包叫做 vue-drag-drop,其 GitHub 地址為:https://github.com/cameronhimself/vue-drag-drop,在目錄下使用此命令安裝:

npm install --save vue-drag-drop  

安裝好了之後我們就可以利用它來實現驗證碼了。

首先 vue-drag-drop 提供了兩個組件,一個叫做 Drag,一個叫做 Drop。前者是被拖動對象,後者是放置目標,我們利用這兩個組件構建兩個滑塊,將 Drag 滑塊拖動到 Drop 滑塊上就成功了。因此,我們要做的僅僅是把它們兩個聲明出來並添加幾個檢測方法就好了,至於拖動的功能,vue-drag-drop 這個組件已經給我們封裝好了。

這裡我們就直接在 App.vue 裏面修改內容就好了,在 <template> 裏面先聲明一下兩個組件:

<template>    <div id="app">      <div id="wrapper" :style="wrapperStyle">        <drop class="drop" id="target"              :class="{ 'over': state.over }"              @dragover="onDragOver"              @dragleave="onDragLeave"              :style="targetStyle">        </drop>        <drag class="drag" id="source"              :transfer-data="true"              @dragstart="onDragStart"              @dragend="onDragEnd"              @drag="onDrag" v-if="!state.dragged"              :style="sourceStyle">          <div slot="image" id="float" :style="sourceStyle">          </div>        </drag>      </div>    </div>  </template>  

很清晰了,一個 <drop> 和一個 <drag> 組件,裏面綁定了一些屬性,下面對這兩個組件的常用屬性作一下說明。

Drop

對於 Drop 組件來說,它是一個被放置的對象,被拖動滑塊會放到這個 Drop 滑塊上,這就代表拖動成功了。它有兩個主要的事件需要監聽,一個叫做 dragover,一個叫做 dragleave,分別用來監聽 Drag 對象拖上和拖開的事件。

在這裡,分別對兩個事件設置了 onDragOver 和 onDragLeave 的回調函數,當 Drag 對象放到 Drop 對象上面的時候,就會觸發 onDragOver 對象,當拖開的時候就會觸發 onDragLeave 事件。

那這樣的話我們只需要一個全局變量來記錄是否已經將滑塊拖動到目標位置即可,比如可以定一個全局變量 state,我們用 over 屬性來代表是否拖動到目標位置。

因此 onDragOver 和 onDragLeave 事件可以這麼實現:

onDragOver() {      this.state.over = true  },  onDragLeave() {      this.state.over = false  }  

Drag

對於 Drag 組件來說,它是一個被拖動的對象,我們需要將這個 Drag 滑塊拖動到 Drop 滑塊上,就代表拖動成功了。它有三個主要的時間需要監聽:dragstart、drag、dragend,分別代表拖動開始、拖動中、拖動結束三個事件,我們這裡也分別設置了三個回調方法 onDragStart、onDrag、onDragEnd。

對於 onDragStart 方法來說,應該怎麼實現呢?這裡應該處理剛拖動的一瞬間的動作,由於我們需要記錄拖動的軌跡,所以聲明一個 trace 全局變量來保存軌跡信息,onDragStart 要做的就是初始化 trace 對象為空,另外記錄一下初始的拖動位置,以便後續計算拖動路徑,所以可以實現如下:

onDragStart(data, event) {      this.init = {          x: event.offsetX,          y: event.offsetY,      }      this.trace = []  }  

對於 onDrag 方法來說,就是處理拖動過程中的一系列拖動動作,這裡其實就是計算當前拖動的偏移位置,然後把它保存到 trace 變量裏面,所以可以實現如下:

onDrag(data, event) {      let offsetX = event.offsetX - this.init.x      let offsetY = event.offsetY - this.init.y      this.trace.push({          x: offsetX,          y: offsetY,      })  }  

對於 onDragEnd 方法來說,其實就是檢測最後的結果了,剛才我們用 state 變量裏面的 over 屬性來代表是否拖動到目標位置上,這裡我們也定義了另外的 dragged 屬性來代表是否已經拖動完成,dragging 屬性來代表是否正在拖動,所以整個方法的邏輯上是檢測 over 屬性,然後對 dragging、dragged 屬性做賦值,然後做一些相應的提示,實現如下:

onDragEnd() {    if (this.state.over) {      this.state.dragging = false      this.state.dragged = true      this.$message.success('拖動成功')    }    else {      this.state.dragging = false      this.state.dragged = false      this.$message.error('拖動失敗')    }    this.state.over = false   }  

OK 了,以上便是主要的邏輯實現,這樣我們就可以完成拖動滑塊的定義以及拖動的監聽了。

接下來就是一些樣式上的問題了,對於圖片的呈現,這裡直接使用 CSS 的 background-image 樣式來設置的,如果想顯示圖片的某一個範圍,那就用 background-position 來設置,這是幾個核心的要點。

好,這裡的樣式設置其實也可以用 JavaScript 來實現,我們把它們定義為一些計算屬性:

wrapperStyle() {    return {      width: this.size.width + 'px',      height: this.size.height + 'px',      backgroundImage: 'url(' + this.image + ')',      backgroundSize: 'cover'    }  },  targetStyle() {    return {      left: this.block.x + 'px',      top: this.block.y + 'px'    }  },  sourceStyle() {    return {      backgroundImage: 'url(' + this.image + ')',      backgroundSize: this.size.width + 'px ' + this.size.height + 'px',      backgroundPosition: -this.block.x + 'px ' + -this.block.y + 'px'    }  }  

另外這裡還有一個值得注意的地方,就是 Drag 組件的 slot 部分:

<div slot="image" id="float" :style="sourceStyle"></div>  

這部分定義了在拖動過程中隨鼠標移動的圖片樣式,這裡也和 Drag 滑塊一樣定義了一樣的樣式,這樣在拖動的過程中,就會顯示一個和 Drag 滑塊一樣的滑塊隨鼠標移動。

最後,就是拖拽完成之後,將滑動軌跡輸出出來,這裡我就直接呈現在頁面上了,<template> 區域加入如下定義即可:

<div>    <p v-if="state.dragged" id="trace">        拖動軌跡:{{ trace }}    </p>  </div>  

好,以上就是一些核心代碼的介紹,還有一些細節的問題可以完善下,比如滑塊隨機初始化位置,以及拖動樣式設置。

最後再看一遍效果:

拖動驗證碼示例

可以看到我們首先拖動了 Drag 滑塊,當 Drag 滑塊拖動到 Drop 滑塊上時,出現了白色描邊,證明已經拖動到目標位置了。然後鬆手之後,觸發 onDragEnd 方法,呈現拖動軌跡,整個驗證碼就驗證成功了。

當然這只是前端部分,如果在這個基礎上添加表單驗證,然後再添加後端校驗,並加上軌跡識別,那可謂是一個完整的驗證碼系統了,在這裡就點到為止啦。

最後如果大家想也仿照實現一下的話,可以參考這個組件:https://github.com/cameronhimself/vue-drag-drop,裏面也介紹了其他的一些屬性和事件,在某些情況下還是很有用的。、