###### tags: `Vue`
@N9_MHei9TA2FRZJGWDx4cg
# Vue+JWT
- [JWT是什麼](#JWT是什麼)
- [流程](#流程)
- [如何透過Vue實作JWT](#如何透過Vue實作JWT)
- [程式](#程式)
- [建立專案並新增頁面](#1-建立專案並新增頁面)
- [帳號密碼驗證流程](#2-帳號密碼驗證流程)
- [Vue-Router-跳轉時攔截](#3-Vue-Router-跳轉時攔截)
## JWT是什麼
JWT全名為JSON Web Token,詳細部分可以參考[這篇文章(JWT — 原理介紹)](https://medium.com/%E4%BC%81%E9%B5%9D%E4%B9%9F%E6%87%82%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88/jwt-json-web-token-%E5%8E%9F%E7%90%86%E4%BB%8B%E7%B4%B9-74abfafad7ba)。
在實作上,其實只要注意不要再jwt裡放敏感的資料,如密碼之類的。因為jwt實際上由3個部分組成,Header、Payload、Signature,而資料都會放在第二部分的Paylaod,但是Header與Payload只會通過Base64進行加密,所以較敏感的資料不適合放在JWT理。
## 流程
1. 伺服器發送JWT
2. 客戶端接收並儲存JWT
3. 每次跳轉時檢查JWT並延長JWT的時間
4. 若JWT不合法則轉回登入頁面
## 如何透過Vue+JWT
- jwt就交給後端做發送和驗證的部分,如此一來jwt所需的密鑰就只會存在後端,減少密鑰外洩的疑慮。
- 前端Vue就透過呼叫<a href="/0UX34j76S_iRuOaKnzR7dw">後端</a>後端去取得jwt。
- 取得jwt後,存在localStorage裡
- 每次Vue Vouter轉到新頁面前都先呼叫後端來檢查jwt是否合法。如果合法就延長jwt的時間,反之則轉到登入頁面。
## 程式
### 1. 建立專案並新增頁面
1. 建立vue-cli專案,詳細說明見[這篇文章](https://hackmd.io/CuJJdABgQBC6AaPHi5BVEQ),使用自訂設定,加入Vue Router。此時目錄應長這樣。
<img src="https://i.imgur.com/GBfo9F1.png" width="200px"/>
2. 建立登入頁面loginView.vue到View資料夾下,LoginView的程式碼如下。
```htmlembedded=
<template>
<div>
<form>
<label>
Account:
<input v-model="account"/>
</label>
<br/>
<label>
Password:
<input v-model="password" type="password"/>
</label>
<br/>
<button>Login</button>
</form>
</div>
</template>
<script>
export default {
data() {
return {
account: "",
password: ""
}
},
methods: {
submit(){
//這邊執行登入
}
}
}
</script>
```
3. 在router/index.js裡新增以下程式碼來新增頁面。
```javascript=
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LoginView from "../views/loginView.vue" //新增的
const routes = [
//......//
{ //新增的
path: '/login', //新增的
name: 'login', //新增的
component: LoginView //新增的
}, //新增的
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
```
>新增完進入 http://localhost:8080/login 會看到以下畫面
<img src="https://i.imgur.com/xt0LkPe.png" />
- 為方便測試,可以在App.vue加入以下程式碼:
```htmlembedded=
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
| <router-link to="/login">Login</router-link> <!--新增的-->
</nav>
<router-view/>
</template>
```
### 2. 帳號密碼驗證流程
1. View的部分處理完後,接下來就是處理帳號密碼發送到伺服器端做驗證,並回傳jwt給我們。
- 假設伺服器架在 http://localhost:65432 上,如懶得架可使用[nodejs](/13lRUwxJRCOK4QV6pPMcoQ)或是[java spring boot](/0UX34j76S_iRuOaKnzR7dw)。
- 在vue.config.js裡新增以下程式碼
```javascript
module.exports = defineConfig({
//...
devServer: { //新增的
proxy: { //新增的
"/api": { //新增的
target: "http://localhost:34567", //新增的
pathRewrite: { '^/api': '' }, //新增的
} //新增的
} //新增的
} //新增的
//...
})
```
- 在此專案的根目錄新增資料夾/api 專門放api相關的檔案,並在其新增login.js。
<img src="https://i.imgur.com/qxcXoxK.png" width="200px"/>
- 安裝axios,login.js會用到
```shell
npm install axios
```
- login.js的程式碼如下:
```javascript=
import axios from "axios"
async function login(userName, password){
const result= {
status: 1,
token: ""
}
const url = "/api/login"
const data = {
username: userName,
password: password
}
const res = await axios.post(url, data)
if(res.data.status){
result.status = 0
result.token = res.data.token
setLoginStore({
isLogin: true,
token: result.token,
userName: res.data.username
})
}
return result
}
async function authToken(token){
const result = {
status: "",
token: "",
userName: ""
}
const url = "/api/auth"
const data = {
token: token
}
const res = await axios.post(url, data)
if(res.status){
result.status = "jwt verify success"
result.token = res.data.token
result.userName = res.data.username
setLoginStore({
isLogin: true,
token: result.token,
userName: result.userName
})
}else{
setLoginStore({
isLogin: false,
token: "",
userName: ""
})
}
return result
}
function setLoginStore(options){
if(options.isLogin){
window.localStorage.setItem("isLogin", options.isLogin)
}
if(options.token){
window.localStorage.setItem("token", options.token)
}
if(options.userName){
window.localStorage.setItem("username", options.username)
}
}
export {login, authToken}
```
- 其中login()與auth()都會呼叫setLoginStore()來將token存在localStorage
- 在/view/loginView.vue新增以下程式碼:
```htmlembedded=
<template>
<div>
<form @submit.prevent="submit()"> //修改
//...
</form>
</div>
</template>
<script>
import { login } from "@/api/login" //新增
import router from "@/router" //新增
export default {
//...
methods: {
async submit(){ //新增
let res = await login(this.account, this.password) //新增
if(res.status==0){ //新增
router.push({name:"home"}) //新增
} //新增
} //新增
}
}
</script>
```
- 如此一來,登入部分就完成了。接下來就只剩下在每次跳轉前驗證token。
<img src="https://i.imgur.com/tbdZirK.gif" />
### 3. Vue Router 跳轉時攔截
1. 要實作這功能,主要用到的是Vue Router的 beforeResolve。詳細功能請見[BeforeResolve](/ArKsDjFuRVOVNQzrz1QQIg#312-beforeResolve)
- 簡單講,BeforeResolve裡的部分會在每次跳轉時執行,那我們只要在其中加入驗證token的部分就可以實作了。
1. 首先,在/router/index.js裡的routes,在想要登入才可進入的頁面中加入以下程式碼,這裡是用Home Page來示範:
```javascript=
const routes = [
{
path: '/',
name: 'home',
component: HomeView,
meta: { //新增
needLogin: true //新增
} //新增
}
//...
]
```
- route的meta屬性是可以自訂的,在這裡使用needLogin的布林值來標記此頁面要登入。
2. 然後,在/router/index.js裡,createRouter之後加入以下程式碼:
```javascript=
import { authToken } from '@/api/login'
router.beforeResolve( async to=>{
if(to.meta.needLogin){
const isLogin = window.localStorage.getItem("isLogin")
if(!isLogin) return {name: "login"}
const token = window.localStorage.getItem("token")
const authResult = await authToken(token)
if(!authResult.status) return {name: "login"}
}
})
```
- 在每次跳轉時,檢查要去的頁面的meta屬性是否有needLogin且為true,有就表示此頁面需要登入。
- 若需要登入則去localStorage裡取相對應的資料。
- 成功的話,在未登入時欲進入HomePage應會轉至login頁面:
<img src="https://i.imgur.com/Bt6ozpX.gif" />
2. 如此一來簡單的jwt功能就做完了,因為資料是存在localStorage裡所以就算重新整理也還是可以驗證。
## 檔案下載
[完整程式碼](https://drive.google.com/file/d/1hWxvzF4NFcfZaH0F5SBGxUv_Jyf9Hf2g/view?usp=share_link)
## 參考文獻
[JSON Web Token](https://jwt.io/)
[透過 JWT 實作驗證機制](https://medium.com/%E9%BA%A5%E5%85%8B%E7%9A%84%E5%8D%8A%E8%B7%AF%E5%87%BA%E5%AE%B6%E7%AD%86%E8%A8%98/%E7%AD%86%E8%A8%98-%E9%80%8F%E9%81%8E-jwt-%E5%AF%A6%E4%BD%9C%E9%A9%97%E8%AD%89%E6%A9%9F%E5%88%B6-2e64d72594f8)
[JWT(JSON Web Token) — 原理介紹](https://medium.com/%E4%BC%81%E9%B5%9D%E4%B9%9F%E6%87%82%E7%A8%8B%E5%BC%8F%E8%A8%AD%E8%A8%88/jwt-json-web-token-%E5%8E%9F%E7%90%86%E4%BB%8B%E7%B4%B9-74abfafad7ba)
[想要資料,令牌要先帶來!- JWT](https://ithelp.ithome.com.tw/articles/10250968)