iT邦幫忙

2022 iThome 鐵人賽

DAY 18
0
Modern Web

Vue+Django+MongoDB+Nginx 全端開發系列 第 18

Json web token authentication --part3: Vue

  • 分享至 

  • xImage
  •  

State management

首先我們先來寫一些前台存 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: {
  }
})

Login

接下來我們來寫一下登入頁面在 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

這邊我們使用巢狀路由來做到每隔一段時間自動刷新 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>

Logout

一樣是在剛剛的 mainLayout.vue 裡,就是最後四行,把 token 從 localStorage 刪掉,再回到登入頁面就好了。

開啟頁面時重新獲得 token 並驗證

我們來到 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 要求有權限才能操作。


上一篇
Json web token authentication --part2: Django
下一篇
Json web token authentication --part4: interceptor & api authentication
系列文
Vue+Django+MongoDB+Nginx 全端開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言