雖說上一節我們實現了登錄功能,但是實際上還是可以通過瀏覽器的地址來跳過登錄訪問到后臺,這種可有可無的登錄功能使得系統沒有安全性,而且沒有意義
為了讓登錄這個功能有意義,我們應該:
- 應當在用戶登錄成功之后給用戶生成一個標記(令牌),將這個令牌保存起來
- 在用戶訪問任意需要登錄的頁面(組件)的時候都要去驗證令牌
- 從而識別到用戶是否登錄或者是否有權限去訪問對應的功能
1.成功時,訪問組件
2.失敗時,進行提示
如何讓login組件中的數據被任意其他組件訪問呢?這個時候可以使用vue官方的狀態管理工具Vuex
Vuex
Vuex是一個專門為Vue.js應用程序開發的狀態管理模式
文檔:
https://vuex.vuejs.org/zh
- Vuex是專門為Vue.js設計的狀態管理庫
- 采用集中式的方式存儲需要共享的數據
- 本質上就是一個JavaScript庫
- 用來解決復雜組件通信,數據共享的問題
簡單來說就是Vuex用來統一存儲需要在多個組件間共享的狀態(數據),狀態可以被任意組件操作,使得組件通信變得易如反掌

那么歸根到底,我們是否需要Vuex,要根據什么來判斷呢?
- 多個視頻依賴于同一個狀態
- 來自不同視圖的行為需要變更同一狀態
安裝Vuex
通過npm安裝:
npm install vuex -S
使用Vue CLI創建項目的時候,可以在項目選項中選擇Vuex,這個時候就不需要再單獨安裝了,這邊我們已經在創建項目的時候安裝過了Vuex,所以不再多做操作
使用
創建Vuex實例store,store通常稱之為"容器"
文件,store/index.js


通過Vue.use()引入Vuex中,Vuex的功能被注入到根實例下的所有子組件中,可以通過$store訪問到內部功能
我們的項目通過VueCLI創建時已經選擇了Vuex,所以創建和引入都已經被Vue CLI自動完成了
State
容器中的state用于存儲需要在組件之間共享的數據
- 容器中的數據可以被任意組件訪問
- 容器中的數據為響應式數據
state存儲count
Vue官方調試工具也可以看到數據
在組件中,通過vm.$store.state.狀態名
訪問
// login/index.vue
methods: {async onSubmit () {console.log(this.$store.state.user)...}
}
Mutation

簡單來說,要修改Vuex的state,必須提前定義Mutation函數,需要的時候再進行提交,Mutation接收state對象作為第一個參數,用于操作state內部的數據
// store/index.js
export default new Vuex.Store({state: {user: 100},mutations: {setUser (state) {state.user++}},actions: {},modules: {}
})
在組件中通過vm.$store.commit('Mutation名稱')
提交Mutation,執行操作
// login/index.vue
methods: {async onSubmit () {console.log(this.$store.state.user)this.$store.commit('setUser')console.log(this.$store.state.user)...}
}
Mutation還接受提交載荷(payload)作為第二個參數,指的是commit()傳入的額外數據,常常需要根據上下文數據修改state使用
// store/index.js
mutations: {setUser (state, payload) {state.user = payload}
},
// login/index.vue
methods: {async onSubmit () {this.$store.commit('setUser', '示例內容')...}
}
但是實際上在大多數情況下,載荷應該是一個對象才對,這樣就可以包含多個字段并且記錄的mutation會更易讀:
mutations: {increment (state, payload) {state.count += payload.amount}
}
store.commit('increment', {amount: 10
})

Mutation的設置方式使得Vuex的狀態修改有跡可循,易于維護,如果state可以通過賦值修改,一旦出錯了就找不到錯誤點了
除此之外,Vue Devtools還提供了Vuex更高級的調試方式Time Travel

Mutation必須為同步函數,由于DevTools提供了Mutation日志功能,為了確保功能正常,內部不能存在異步任務,否則DevTools將無法得知Mutation的準確調用順序,如果需要進行異步操作,那么則需要Vuex的Action
Action
Action類似于Mutation,不同的地方在于:
- Action提交的是Mutation,而不是直接變更狀態
- Action可以包含任意的異步操作
Action 函數接受一個與store實例具有相同方法和屬性的context對象,因為可以調用context.commit
提交一個Mutation
// store/index.js
actions: {addAction (context) {setTimeout(function () {context.commit('setUser')}, 1000)}
},
在實機操作中,我們經常用到ES2015的參數結構來簡化代碼(就比如我們在解構后端傳來的數據一樣,直接{ data }),這種情況尤其是出現在我們需要多次調用commit
的時候
actions: {// jia (context) {// context.commit('jia')// }jia ({ commit }) {commit('jia')}},
Action 通過 vm.$store.dispatch
方法觸發,參數1為action名稱,參數2為payload
// login/index.vue
methods: {async onSubmit () {this.$store.dispatch('addAction')...}
}
Vuex 核心概念還有Getter和Module,可以通過文檔來進行學習
身份認證
登錄狀態存儲
獲取能夠在任意組件中訪問用戶的登錄信息,我們將狀態存儲在Vuex中
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({state: {user: null},mutations: {setUser (state, payload) {state.user = payload}},actions: {},modules: {}
})
聲明Mutation拿來用于修改user數據,具體內容采用payload載荷方式傳入,我們可以通過devtools調試工具查看
觀察到通過接口傳來的數據,我們應該拿來存入的是data.content,內部為用戶的相關信息。
// login/index.js
methods: {async onSubmit () {try {...// 當登錄成功時,記錄登錄狀態,存儲到 Vuex 中this.$store.commit('setUser', data.content)this.$router.push({name: 'home'})this.$message.success('登錄成功')} catch (err) {console.log('驗證失敗', err)}}
}
那么又因為傳來的數據是JSON格式,為了以后方便我們的使用,我們應該在Mutation的setUser中轉換為對象保存,可以通過DevTools調試工具查看到,然后就是通過本地存儲的方式對user進行數據持久化,避免頁面刷新后丟失,存儲成功之后呢,就可以將user的初始值更改為本地存儲獲取user的數據
state: {// 用于登錄成功后保存用戶信息的(初始值嘗試讀取本地存儲user: JSON.parse(window.localStorage.getItem('user') || null)},mutations: {// 存儲用戶數據setUser (state, payload) {// 將payload轉換為對象后進行存儲state.user = JSON.parse(payload)// 將payload的數據添加到本地存儲中// 本地存儲只能存儲字符串window.localStorage.setItem('user', payload)}},
校驗頁面訪問權限
路由跳轉時,需要校驗登錄狀態,根據結果進行后續處理
這里使用Vue Router的導航守衛beforeEach,在任務導航被觸發的時候進行登錄狀態監測,當前后臺頁面都需要登錄狀態,但是有些頁面不需要登錄狀態的話,這個該如何處理呢?
使用:Vue Router的路由元信息功能來設置
下面給需要設置登錄狀態的路由添加路由元信息(比如我們把home頁面的子路由的部分設置為需要登錄)
- meta用于保存與路由相關的自定義數據
-
requiresAuth表示是否需要認證,true為需要
用戶登錄狀態保存在store(Vuex)中,需要引入文件來讀取數據檢測,在導航守衛中檢測to的路由是否需要登錄
// 全局前置守衛
router.beforeEach((to, from, next) => {// 驗證 to 路由記錄是否需要進行身份認證if (to.matched.some(record => record.meta.requiresAuth)) {// 驗證Vuex 的 store 中的用戶信息是否存儲if (!store.state.user) {// 未登錄,跳轉到登錄頁面return next({ name: 'login' })}// 已經登錄,允許通過next()} else {// 無需登錄,允許通過next() // 確保一定要調用 next()}
})
登錄后跳轉到上次訪問頁面
上一次我如果訪問了用戶管理/user,過了一段時間狀態過期,直接訪問路由/user,跳轉到login,登陸之后又跳轉了首頁。如果我希望登錄后直接跳轉到user而不是首頁,我們就應該在每次跳轉到/login時記錄當前to目標的路由信息,這個時候就可以通過跳轉路由的query屬性進行設置
//router/index.jsif (!store.state.user) {// 未登錄,跳轉到登錄頁面next({name: 'login',query: {// 將本次路由的fullpath傳遞給login頁面// path僅包含路徑,fullpath為完整url(包含了查詢字符串參數等信息)redirect: to.fullPath}})}
那么在登錄頁中,登陸成功的跳轉時,應該檢測是否存在登錄前的頁面信息,有就跳轉,沒有就默認跳轉首頁

除了登錄過期以外,例如將頁面存儲書簽,或者點擊其他人發送的鏈接訪問,都可以在登陸之后自動跳轉到對應的路由,提高了我們的體驗

用戶信息與接口鑒權
用戶基本信息接口:接口
首先使用postman進行接口測試
在集合中添加一個新的請求,設置基本信息

發送請求,發現傳回來的是錯誤信息

這個時候就說明接口需要授權才能訪問,查詢了接口的說明文檔可以得知,需要一個名為“Authotization”的請求參數(位于請求頭),用于驗證 授權信息,這種驗證接口的授權的處理方式我們稱之為接口鑒權

得出兩條結論:
- 后端提供的接口是沒法隨便訪問的
- 使用接口前需要進行接口的鑒權處理
那么我們該怎么獲得權限去獲取后端返回的數據呢?
Token
一種常用的接口鑒權方式
Token是在用戶登陸成功之后,由服務端生成的一段保存了用戶身份信息,加密的字符串
生成之后,通過響應方式將token信息響應到服務端,通過之前的登錄接口響應成功時可以看到



Postman統一設置token
后續我們要用到的這個集合的接口會更多,每次都寫Token的話就要被煩到,所以我們可以使用Postman提供的統一設置方式



保存了之后,回到用戶信息接口會發現我們單獨設置的authorization提示了已經被統一設置的信息,我們就可以將自己設置的給刪除掉,再次發送請求也是沒有問題的
實現用戶信息展示
測試處理完畢之后,我們需要在代碼中進行Token處理和功能實現
首先要封裝用于用戶信息請求的方法,在header最后那個設置Token,引入store用于讀取token

接著在app-header組件中引入功能,并且在created鉤子函數中請求數據,并將其數據綁定到視圖中
<script>
// 引入用戶信息接口功能
import { getUserInfo } from '@/services/user'export default {name: 'AppHeader',created () {// 加載用戶信息// 鉤子函數不建議書寫代碼邏輯,最好直接使用封裝好的函數this.loadUserInfo()},data () {return {// 用戶信息userInfo: {}}},methods: {async loadUserInfo () {const { data } = await getUserInfo()// console.log(data)this.userInfo = data.content}}
}
</script>

通過請求攔截器設置Token
通過Axios的請求攔截器進行統一設置Token
很多請求都是需要在header設置Token信息的,可以通過Axios攔截器進行統一處理
Axios攔截器與導航守衛相似,可以在任意請求和響應前進行攔截處理,功能分為:
- 請求攔截器
- 響應攔截器
通過請求攔截器參數config.headers可以訪問請求頭,將store中的Token統一設置就可以了
// 設置請求攔截器
request.interceptors.request.use(function (config) {
// 判斷config.url的前綴是什么,然后進行請求baseURL的設置config.baseURL = getBaseURL(config.url)// 統一的token信息設置// 為了嚴謹,可以讀取store中的user后進行Token檢測const { user } = store.stateif (user && user.access_token) {// 設置tokenconfig.headers.Authorization = user.access_token}return config
})
這個攔截器設置之后呢,services/user.js中的getUserInfo內部的Token設置就可以刪除了,同時也可以去掉store的引入
// services/user.js
...
// import store from '@/store'
...
// 用戶基本信息請求
export const getUserInfo = () => {return request({method: 'GET',url: '/front/user/getInfo'// 在 header 中設置 Token 信息(統一設置后去除,記得去除上一行的分號)/* headers: {Authorization: store.state.user.access_token} */})
}
用戶退出
首先我們要給退出按鈕設置點擊事件,發現其實是觸發不了的,那是因為我們使用的是Element組件,這里的退出按鈕是組件,而組件設置的都是自定義事件
// app-header.vue
<el-dropdown-itemdivided@click="handleLogout"
>退出</el-dropdown-item>...methods: {...// 退出按鈕功能handleLogout () {console.log('點擊退出')}
}
我們可以使用事件修飾符.native來監聽組件根元素的原生事件
// app-header.vue
<el-dropdown-itemdivided@click.native="handleLogout"
>退出</el-dropdown-item>
點擊退出后,清除store內部的用戶信息,同時跳轉回到登錄頁面
- 通過mutation中的setUser清空user,由于setUser也設置了本地存儲,這個時候也會自動清空
// 退出功能handleLogout () {// 清除store的用戶信息this.$store.commit('setUser', null)// 跳轉登錄頁this.$router.push('/login')}
可以通過Element的MessageBox彈框組件進行退出確認提示,增強體驗感
- 確認提示使用&confirm(),確定觸發then(),取消觸發catch()
- 按鈕內容默認為確定和取消,如不更改可以刪除
在點擊退出按鈕時,通過this,$confirm()
進行確認消息設置 - 將前面設置的退出功能移步到位確認退出的代碼區域
// 退出功能handleLogout () {this.$confirm('確認退出嗎?', '退出提示', {confirmButtonText: '確定',cancelButtonText: '取消',type: 'warning'}).then(() => {// 清除store的用戶信息this.$store.commit('setUser', null)// 跳轉登錄頁this.$router.push('/login')this.$message({type: 'success',message: '退出成功!'})}).catch(() => {this.$message({type: 'info',message: '取消退出'})})}
以上,登錄功能到認證到退出一個閉環功能完成了,完善程度很高
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務

喜歡的朋友記得點贊、收藏、關注哦!!!