首先我們先來寫一些前台存 token 和刪除 token 的方法在 vuex sate management,檔案位置在 src/store/index.ts
import { createStore } from 'vuex'
export default createStore({
state: {
authTokens: localStorage.getItem('authTokens') || {'refresh':null,'access':null}
},
mutations: {
setToken (state,newAuthTokens) {
state.authTokens =newAuthTokens
localStorage.setItem("authTokenRefresh",newAuthTokens['refresh']);
localStorage.setItem("authTokenAccess",newAuthTokens['access']);
},
delToken (state) {
state.authTokens = {'refresh':null, 'access':null};
localStorage.removeItem("authTokenRefresh")
localStorage.removeItem("authTokenAccess")
},
},
actions: {
},
modules: {
}
})
接下來我們來寫一下登入頁面在 src/components/login.vue
,以下是 script 的部分,主要就是去 post /api/token
,驗證過的話就把 token 存在 localStorage
,然後進入首頁,存 token 的方法剛剛寫在 stage management 了,這裡就引入方法並使用
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { useStore } from 'vuex'
import axios from 'axios'
import { ElMessage } from 'element-plus'
// vuex ############################
const store = useStore()
// login ###########################
let username=ref('')
let password=ref('')
const login=()=>{
axios({
method: 'post',
url:'http://localhost:8000/api/token/',
data: {
username: username.value,
password: password.value
},
})
.then(res=>{
// restore token to stage management and local storage
store.commit('setToken',res.data)
if (res.status==200){
router.push('/')
}
else{
ElMessage({
message: 'username or password is wrong',
type: 'error',
})
}
})
.catch(err=>{
ElMessage({
message: 'username or password is wrong',
type: 'error',
})
})
}
</script>
這邊我們使用巢狀路由來做到每隔一段時間自動刷新 token 的功能,我們做一個 src/components/mainLayout.vue 當作父頁面,在父頁面這邊做到刷新 token 的功能,其他需要做到這個功能的頁面,都掛在這個頁面之下當作子頁面。
在上一篇中,我們設定 access token 失效時間為五分鐘,所以前台這邊我們設定每四分鐘刷新一次 token,這樣使用者只要瀏覽器開著,就可以保持登入狀態了。
然後我們要用 jwt-decode
這個套件去解析 access token 中的 payload,在上一篇中,我們把 username 加到了 payload 中,現在我們用 const user=jwt_decode<{'username':''}>(String(localStorage.getItem('authTokenAccess')))['username']
獲得 username 資訊,這樣就可以使用這個資訊囉
// src/components/mainLayout.vue script 部分
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router';
import { useStore } from 'vuex'
import axios from 'axios'
import jwt_decode from 'jwt-decode'
// vuex ############################
const store = useStore()
const user=jwt_decode<{'username':''}>(String(localStorage.getItem('authTokenAccess')))['username']
// route ###########################
const router = useRouter()
let now=new Date()
// get username after verify token ######
// refresh token every 4 minutes ########
setInterval(()=>{
axios({
method: 'post',
url:'http://localhost:8000/api/token/refresh/',
data: {
refresh:localStorage.getItem('authTokenRefresh')
},
})
.then(res=>{
store.commit('setToken',res.data)
})
},1000*60*4)
const logout=()=>{
store.commit('delToken')
router.push('/login')
}
<script>
一樣是在剛剛的 mainLayout.vue
裡,就是最後四行,把 token 從 localStorage 刪掉,再回到登入頁面就好了。
我們來到 vue 路由設定這邊,檔案位在 src/router/index.ts
,主要是要在 router.beforeEach
這邊做一個路由守護,進入任何頁面之前先去刷新 token 再進行驗證,
由於 access token 五分鐘就會失效,當使用者登入之後再關掉網頁,下次開頁面的時候肯定已經失效了,所以要先用 refresh token 去向後端要求新的一組 token,並存到 localStorage,驗證過了就進想去的頁面,否則就進到登入頁面
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
// check if the token in localStorage is valid ###############
router.beforeEach((to, from) => {
// refresh token #######################
axios({
method: 'post',
url:'http://localhost:8000/api/token/refresh/',
data: {
refresh:localStorage.getItem('authTokenRefresh')
},
})
.then(res=>{
if(res.status==200){
// restore new token to cookie ###
store.commit('setToken',res.data)
}
else{
router.push('/login')
}
})
.then(res=>{
// verify token #####################
// if valid -> go to 'to'
// if not -> go to '/login'
axios({
method: 'post',
url:'http://localhost:8000/api/token/verify/',
data: {
token:localStorage.getItem('authTokenAccess')
},
})
.then(res=>{
// if token valid #############
if(res.status==200){
router.push(to)
}
else{
// if token is not valid ####
router.push('/login')
}
})
.catch(err=>{
// if token is not valid ######
router.push('/login')
})
})
.catch(err=>{
// if token is not valid ######
router.push('/login')
})
})
好了,最基本的驗證到這裡就講完了,一般應用,驗證做到這裡已經很好了。
不過有個問題,以上都是針對使用者用瀏覽器操作網頁,只會去擋使用者在前端的操作,那如果使用者或者駭客知道你的後端 api 呢?他直接 request 後端呢?直接繞過前台不就不用驗證了嗎?
下一篇我們會對這個問題提出解決辦法,透過前端攔截器,以及後端 api 要求有權限才能操作。