一、geetest滑動驗證
geetest官方文檔地址:https://docs.geetest.com/
產品——極速驗證:基於深度學習的人機識別應用。極驗「行為驗證」是一項可以幫助你的網站與APP識別與攔截機器程序批量自動化操作的SaaS應用。它是由極驗開發的新一代人機驗證產品,它不基於傳統“問題-答案”的檢測模式,而是通過利用深度學習對驗證過程中產生的行為數據進行高維分析,發現人機行為模式與行為特征的差異,更加精准地區分人機行為。
1、web部署介紹
客戶端官方文檔:https://docs.geetest.com/install/deploy/client/web/
(1)引入初始化函數
通過引入 gt.js 文件,引入 initGeetest 初始化函數。
<script src="gt.js"></script>
注意:行為驗證要求初始化在業務頁面加載時同時初始化,否則驗證無法讀取用戶在業務頁面操作的行為數據,導致驗證策略失效。
這里的 gt.js 文件,它用於加載對應的驗證JS庫。在每個后端語言的sdk中都存有一份,開發者部署到實際環境需要將該文件復制到相應的項目中使用。
之前該文件地址是 https://static.geetest.com/static/tools/gt.js ,改為存放在用戶的項目中,防止靜態服務器出問題無法加載該文件。
(2)調用初始化函數初始化
使用初始化函數 initGeetest
初始化后,回調的第一個參數即是驗證實例,它的第二個參數是一個回調,如下代碼所示。
ajax({ url: "API1接口(詳見服務端部署)", type: "get", dataType: "json", success: function (data) { //請檢測data的數據結構, 保證data.gt, data.challenge, data.success有值 initGeetest({ // 以下配置參數來自服務端 SDK gt: data.gt, challenge: data.challenge, offline: !data.success, new_captcha: true }, function (captchaObj) { // 這里可以調用驗證實例 captchaObj 的實例方法 }) } })
注: 對於同一個頁面存在多個驗證碼場景的初始化,需要每個驗證碼場景調用 initGeetest 方法單獨進行初始化;如果一個場景下有多個驗證入口,需要進行多次初始化。
(3)product參數設置二級驗證
在行為驗證中,絕大多數真實用戶僅需點擊一下即可通過驗證。但是考慮到實際風險情況,被行為驗證判定為有風險的請求,會進入下個階段的驗證。此時,行為驗證提供了彈出二級驗證的交互樣式,方便用戶兼容自己本身的界面。這里以float浮動式驗證為例:
initGeetest({ // 省略必須的配置參數 product: 'float' }, function (captchaObj) { captchaObj.appendTo("#captchaBox"); //將驗證按鈕插入到宿主頁面中captchaBox元素內 captchaObj.onReady(function(){ //your code }).onSuccess(function(){ //your code }).onError(function(){ //your code }) });
2、vue項目Login頁面geetest實現
觀察各大網站使用了geetest的login頁面接口信息,可以發現請求發回的數據中包含gt、challenge、success的值。在單頁面應用Login.vue中實現geetest驗證。
(1)在項目中全局引入geetest
在前端項目中創建/src/global/gt.js文件,寫入文件地址 https://static.geetest.com/static/tools/gt.js 的內容。
在/src/main.js中全局引入gt.js文件:
import '../static/global/gt.js'
(2)添加geetest接口
在 /src/restful/api.js 中添加geetest接口如下所示:
// geetest接口 export const geetest = ()=>{ return Axios.get('captcha_check/').then(res=>res.data); }
(3)配置getGeetest方法
在getGeetest方法中調用geetest初始化函數初始化:
<script> export default { name: 'Login', data(){ return { username: "", password: "" } }, methods:{ getGeetest(){ this.$http.geetest() .then(res=>{ console.log(res); let data = res.data; //請檢測data的數據結構, 保證data.gt, data.challenge, data.success有值 initGeetest({ // 以下配置參數來自服務端 SDK gt: data.gt, // 驗證id,極驗后台申請得到 challenge: data.challenge, // 驗證流水號,后服務端SDK向極驗服務器申請得到 offline: !data.success, // 極驗API服務器是否宕機(即處於fallback狀態) new_captcha: true, // 宕機情況下使用,表示驗證是3.0還是2.0,3.0的sdk該字段為true product: popup, // 彈出式展現 with: '100%' // 默認寬度300px }, function (captchaObj) { // 這里可以調用驗證實例 captchaObj 的實例方法 captchaObj.appendTo("#geetest"); //將驗證按鈕插入到宿主頁面中captchaObj元素內 captchaObj.onReady(function(){ //your code }).onSuccess(function(){ //your code }).onError(function(){ //your code }) }) }) .catch(err=>{ console.log(err); }) } }, created() { this.getGeetest(); } }; </script>
(4)appendTo(position)
appendTo
方法用於將驗證按鈕插到宿主頁面,使其顯示在頁面上。接受的參數可以是 id 選擇器(例如 #captcha-box
),或者 DOM 元素對象。
<div id="captcha-box"></div> // 方式一:傳入id選擇器 <script> initGeetest({ // 省略配置參數 }, function (captchaObj) { captchaObj.appendTo('#captcha-box'); // 省略其他方法的調用 }); </script> // 方式二:傳入DOM元素 <script> var captchaBox = document.getElementById('#captcha-box'); initGeetest({ // 省略配置參數 }, function (captchaObj) { captchaObj.appendTo(captchaBox); // 省略其他方法的調用 }); </script>
(5)onReady(callback)
監聽驗證按鈕的 DOM 生成完畢事件。參數 callback
為函數類型。
<div id="captcha-box"> <div id="loading-tip">加載中,請稍后...</div> </div> <script> initGeetest({ // 省略配置參數 }, function (captchaObj) { captchaObj.appendto('#captcha-box'); // 省略其他方法的調用 captchaObj.onReady(function () { // DOM 准備好后,隱藏 #loading-tip 元素 // 僅作示例用,用您適合的方式隱藏即可 document.getElementById('loading-tip').style.display = 'none'; }); }); </script>
(6)geetest校驗顯示效果
顯示效果如下所示:
二、二次驗證數據處理和登錄實現
1、getValidate()方法二次校驗
獲取用戶進行成功驗證(onSuccess
)所得到的結果,該結果用於進行服務端 SDK 進行二次驗證。getValidate
方法返回一個對象,該對象包含 geetest_challenge
,geetest_validate
,geetest_seccode
字段。
export default { name: 'Login', data(){ return { username: "", password: "", validateResult: {} // 驗證成功后返回的結果,用於服務端sdk二次驗證 } }, methods:{ getGeetest(){ this.$http.geetest() .then(res=>{ console.log(res); let data = res.data; // 將當前組件this對象賦值給 _this var _this = this; // 函數的調用方式決定了this的值。this不能在執行期間被賦值,並且在每次函數被調用時this的值也可能會不同。 //請檢測data的數據結構, 保證data.gt, data.challenge, data.success有值 initGeetest({ // 以下配置參數來自服務端 SDK gt: data.gt, // 驗證id,極驗后台申請得到 challenge: data.challenge, // 驗證流水號,后服務端SDK向極驗服務器申請得到 offline: !data.success, // 極驗API服務器是否宕機(即處於fallback狀態) new_captcha: true, // 宕機情況下使用,表示驗證是3.0還是2.0,3.0的sdk該字段為true product: popup, // 彈出式展現 with: '100%' // 默認寬度300px }, function (captchaObj) { // 這里可以調用驗證實例 captchaObj 的實例方法 captchaObj.appendTo("#geetest"); //將驗證按鈕插入到宿主頁面中captchaObj元素內 captchaObj.onReady(function(){ //your code }).onSuccess(function(){ //your code console.log(captchaObj); var result = captchaObj.getValidate(); _this.validateResult = result; }).onError(function(){ //your code }) }) }) .catch(err=>{ console.log(err); }) }
2、配置登錄接口
在 src/restful/api.js 文件中配置登錄接口:
// 登錄接口 export const userLogin = (params)=>{ // 這個參數至少有5個字段,username,password,geetest_challenge,geetest_validate,geetest_seccode return Axios.post('account/login/', params).then(res=>res.data); };
3、登錄事件
在登錄按鈕上綁定登錄事件:
<button class="login_btn" @click="loginHandler">登錄</button> <p class="go_login" >沒有賬號 <span>立即注冊</span></p>
添加loginHandler方法:
methods:{ loginHandler(){ let params = { // 5個字段 username: this.username, password: this.password, geet_challenge: this.validateResult.geet_challenge, geet_validate: this.validateResult.geet_validate, geet_seccode: this.validateResult.geet_seccode }; this.$http.userLogin(params) .then(res=>{ console.log(res); }) .catch(err=>{ console.log(err); }) },
在頁面登錄查看控制台輸出的data信息:
4、登錄數據解析
在二次驗證成功后,通過編程式導航跳轉到Home組件,顯示網站首頁,但是跳轉時需要攜帶驗證時獲取的登錄數據信息。
這里使用localstorage來存儲登錄信息:
methods:{ loginHandler(){ let params = { // 5個字段 username: this.username, password: this.password, geet_challenge: this.validateResult.geet_challenge, geet_validate: this.validateResult.geet_validate, geet_seccode: this.validateResult.geet_seccode }; this.$http.userLogin(params) .then(res=>{ console.log(res); if (res.error_no === 0){ // 驗證成功 this.$router.push({ // 路由跳轉到Home組件 name: "Home" }); localStorage.setItem('access_token', res.data.access_token); // token值判斷是否登錄 localStorage.setItem('username', res.data.username); // 用戶名 localStorage.setItem('avatar', res.data.avatar); // 用戶頭像 } }) .catch(err=>{ console.log(err); }) },
查看控制台Application中顯示的Local Storage信息:
只讀的localStorage
屬性允許你訪問一個Document
源(origin)的對象 Storage
;其存儲的數據能在跨瀏覽器會話保留。localStorage
類似 sessionStorage
,但其區別在於:存儲在 localStorage
的數據可以長期保留;而當頁面會話結束——也就是說,當頁面被關閉時,存儲在 sessionStorage
的數據會被清除 。
無論數據存儲在 localStorage
還是 sessionStorage
,它們都特定於頁面的協議。
另外,localStorage
中的鍵值對總是以字符串的形式存儲。 (需要注意, 和js對象相比, 鍵值對總是以字符串的形式存儲意味着數值類型會自動轉化為字符串類型).
5、用戶登錄后組件通信問題
登錄點擊后,頁面跳轉至網站首頁(Home組件),同時也使用了localStorage存儲了需要保存的對象。但是要想將存儲的用戶名和頭像展示到頁首(Header組件)卻無法完成。Home組件和Header組件沒有關聯。需要使用 Vuex 插件,集中式存儲管理應該的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式變化。
三、vue-cli項目中集成Vuex
1、npm安裝vuex
vue-cli構建的項目中不包含vuex,手動引入:
$ npm install vuex -S
2、vuex入口文件配置
安裝 Vuex 后,創建 src/store 目錄,再創建 src/store/index.js 文件,用作組裝模塊並導出store。
import Vue from 'vue' import Vuex from 'vuex' //引入vuex // 使用插件 Vue.use(Vuex); //創建vuex中的store對象 let store = new Vuex.Store({ // 三大將 state:{ userInfo: {} }, // 修改state的唯一方法:提交mutations mutations:{ getUserInfo(state, user){ state.userInfo = user; } }, actions:{ getUserInfo({commit}, user){ commit('getUserInfo', user) } } }); export default store; // 拋出store對象
3、main.js中引入vuex的store
import Vue from 'vue' import Vuex from 'vuex' import App from './App' import router from './router' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import '../static/global/global.css' import '../static/global/gt.js' Vue.use(ElementUI); Vue.use(Vuex); import * as api from './restful/api' console.log(api); Vue.prototype.$http = api; // store引入 import store from '../src/store/index' Vue.config.productionTip = false; new Vue({ el: '#app', router, store, // store對象掛載到vue實例中 components: { App }, template: '<App/>' });
將store對象掛載到vue實例中后,各個組件中可以通過 this.$store獲取當前的store對象。
因此可以通過 this.$store.state.userInfo來獲取當前的用戶對象。
4、Login組件中將用戶信息保存到state中
前面已經在Login組件中寫了 loginHandler 方法,來處理登錄事件。
驗證成功后將用戶信息保存到localStorage中,這里還要將用戶信息保存到 store對象的 userInfo 字段中。改寫如下所示:
methods:{ loginHandler(){ let params = { // 5個字段 username: this.username, password: this.password, geet_challenge: this.validateResult.geet_challenge, geet_validate: this.validateResult.geet_validate, geet_seccode: this.validateResult.geet_seccode }; this.$http.userLogin(params) .then(res=>{ console.log(res); if (res.error_no === 0){ // 驗證成功 this.$router.push({ // 路由跳轉到Home組件 name: "Home" }); localStorage.setItem('access_token', res.data.access_token); // token值判斷是否登錄 localStorage.setItem('username', res.data.username); // 用戶名 localStorage.setItem('avatar', res.data.avatar); // 用戶頭像 localStorage.setItem('shop_cart_num', res.data.shop_cart_num); // 購物車數量 // dispatch action的行為 this.$store.dispatch('getUserInfo', res.data); } }) .catch(err=>{ console.log(err); }) },
5、Header組件中監聽用戶信息更新信息顯示
頁首組件在用戶沒有登錄是會顯示登錄、注冊按鈕。在用戶注冊后應顯示用戶名及用戶頭像等信息。
(1)實時監聽userInfo數據
<script> export default { name: 'LuffyHeader', data() { return { headerList: [ {id: '1', name: 'Home', title: '首頁'}, {id: '2', name: 'Course', title: '免費課程'}, {id: '3', name: 'LightCourse', title: '輕課'}, {id: '4', name: 'Micro', title: '學位課程'} ] } }, computed: { userInfo(){ return this.$store.state.userInfo; } } }; </script>
methods、watch、computed對比:
- computed 屬性的結果會被緩存,除非依賴的響應式屬性變化才會重新計算。主要當作屬性來使用;
- methods 方法表示一個具體的操作,主要書寫業務邏輯;
- watch 一個對象,鍵是需要觀察的表達式,值是對應回調函數。主要用來監聽某些特定數據的變化,從而進行某些具體的業務邏輯操作;可以看作是 computed 和 methods 的結合體;
(2)template中修改登錄前后模板顯示
判斷userInfo.access_token是否有值,沒有值顯示登錄/注冊,有值則顯示登錄信息。
<div class="nav-right" v-if="userInfo.access_token"> <span class = 'el-dropdown-link'>學習中心</span> <span class="user">{{userInfo.username}}</span> <img :src="userInfo.avatar" alt=""> <ul class="my_account"> <li>我的賬戶<i>></i></li> <li>我的訂單<i>></i></li> <li>我的優惠券<i>></i></li> <li>我的消息<span class="msg">({{userInfo.notice_num}})</span><i>></i></li> <li>購物車<span class="count">({{userInfo.shop_cart_num}})</span></li> <li>退出<i>></i></li> </ul> </div> <!-- </el-dropdown> --> <div class="nav-right" v-else> <span>登錄</span> | <span>注冊</span> </div>
(3)登錄后頁面顯示如下所示
6、鼠標懸浮顯示/隱藏下拉框
使用v-show來控制無序列表 <ul> 標簽的顯示和隱藏。
(1)設置 ul 標簽默認隱藏
<template> <!-- 代碼略 --> <div class="nav-right" v-if="userInfo.access_token"> <span class = 'el-dropdown-link'>學習中心</span> <span class="user">{{userInfo.username}}</span> <img :src="userInfo.avatar" alt=""> <ul class="my_account" v-show="isShow"> <li>我的賬戶<i>></i></li> <li>我的訂單<i>></i></li> <li>我的優惠券<i>></i></li> <li>我的消息<span class="msg">({{userInfo.notice_num}})</span><i>></i></li> <li>購物車<span class="count">({{userInfo.shop_cart_num}})</span></li> <li>退出<i>></i></li> </ul> </div> </template> <script> export default { name: 'LuffyHeader', data() { return { headerList: [ // 代碼略 ], isShow: false // 默認隱藏 } }, computed: { userInfo(){ return this.$store.state.userInfo; } } }; </script>
(2)鼠標懸浮顯示ul
isShow默認值為false,即默認隱藏。鼠標移入觸發enterHandler事件后ul 顯示,鼠標移出觸發leaveHandler事件后ul隱藏。
<template> <!--代碼略--> <div class="nav-right" v-if="userInfo.access_token" @mouseenter="enterHandler" @mouseleave="leaveHandler"> <span class = 'el-dropdown-link'>學習中心</span> <span class="user">{{userInfo.username}}</span> <img :src="userInfo.avatar" alt=""> <ul class="my_account" v-show="isShow"> <li>我的賬戶<i>></i></li> <li>我的訂單<i>></i></li> <li>我的優惠券<i>></i></li> <li>我的消息<span class="msg">({{userInfo.notice_num}})</span><i>></i></li> <li>購物車<span class="count">({{userInfo.shop_cart_num}})</span></li> <li>退出<i>></i></li> </ul> </div> <!-- </el-dropdown> --> <div class="nav-right" v-else> <span>登錄</span> | <span>注冊</span> </div> <!--代碼略--> </template> <script> export default { name: 'LuffyHeader', data() { return { headerList: [ // 代碼略 ], isShow: false // 默認隱藏 } }, methods: { enterHandler() { this.isShow = true; }, leaveHandler() { this.isShow = false; } }, computed: { userInfo(){ return this.$store.state.userInfo; } } }; </script>
四、全局守衛保持用戶始終登錄
前面實現了用戶登錄,Header顯示用戶登錄信息。但是如果刷新頁面或者跳轉到導航欄其他頁面。會發現Header顯示的不是用戶登錄信息,而是 登錄/注冊。
因此需要使用Vue-Router 的導航守衛來保存用戶始終登錄。
1、在main.js中引入路由全局守衛
// store引入 import store from '../src/store/index' // 路由全局守衛 router.beforeEach((to, from, next)=>{ console.log(to); console.log(from); next(); // 確保要調用 next 方法,否則鈎子就不會被 resolved(發生阻塞) }); Vue.config.productionTip = false;
控制台查看to,from打印信息:
from是從哪里來的路由(當前導航要離開的路由),to是到哪里去的路由(即將要進入的目標路由對象)。
2、分發Action獲取用戶信息
在main.js 中讀取localStorage中存儲的用戶信息。通過 store.dispatch
方法觸發Action中的 getUserInfo 方法。
// 路由全局守衛 router.beforeEach((to, from, next)=>{ // console.log(to); // console.log(from); if(localStorage.getItem('access_token')){ // 用戶登錄過了 let user = { // 獲取用戶信息 access_token: localStorage.getItem('access_token'), username: localStorage.getItem('username'), avatar: localStorage.getItem('avatar'), shop_cart_num: localStorage.getItem('shop_cart_num') }; // 通過dispatch調用action中方法getUserInfo store.dispatch('getUserInfo', user); } next(); // 確保要調用 next 方法,否則鈎子就不會被 resolved(發生阻塞) });
提交mutation后,實現更改 Vuex 的 store 的 state。
Header組件中 compute 監聽到userInfo變化,因此刷新頁面或者跳轉到導航欄其他頁面時,都會正常顯示用戶登錄信息。