Vue項目 使用攔截器和JWT驗證 完整案例

幾乎在全部的項目中都離不開攔截器登陸驗證,這是必需的。若是你學會了這個demo,那麼幾乎全部網站的登陸驗證,加載動畫就都會了,因此背也要背會css

因此本章以一個demo爲例,來幫助你們理解攔截器登陸驗證控制vue

文章後面有源碼,能夠下載下來運行一下ios

先來看看效果:git

圖片加載失敗!

功能:web

  • 當你訪問首頁的時候,會有一個加載動畫,就是攔截器的功勞,而且首頁會有一個當前登陸的用戶名,默認是wangcai,等你登陸成功後,會替換成你本身登陸的用戶名ajax

  • 當你沒有登陸的時候,能夠訪問首頁和登陸頁,可是訪問不了我的中心(Profile),當你訪問我的中心,會給你自動跳轉到登陸頁vue-router

  • 當你在登陸頁進行登陸,若是用戶名輸入錯誤的話,會彈出錯誤信息vuex

  • 當你輸入正確的時候(我設置了Fan爲正確的用戶名),點擊登陸,登陸成功後,會自動給你跳轉到首頁express

  • 而且登陸成功後,若是你再點擊想進入登陸頁,是不行的,他會自動給你跳轉到首頁json

  • 登陸成功後,就能夠訪問 我的中心頁面

  • 若是你超過 20秒 不對頁面進行操做(我設置的是20秒,能夠自行設置),那麼token會自動失效,那麼你就訪問不了我的中心,你須要再次登陸

  • 若是你在 20秒 以內,操做頁面的話,那麼token的值是不會失效的,因此是不須要再次登陸的。也就是說,在 20秒 以內,你每次進行路由跳轉的時候,token的值和時間就會自動重置,防止失效讓你再次登陸(總不能讓你看着看着忽然讓你登陸)

下面就讓咱們開始吧!!! (有關代碼的解釋說明已在代碼中註釋

案例

使用攔截器並封裝axios

新建一個Vue項目(vue create demo)

刪去沒必要要的文件和代碼,經典化代碼

圖片加載失敗!

安裝須要的依賴:

package.json文件部分代碼:

"dependencies": {
	"axios": "^0.19.0",
	"body-parser": "^1.19.0",
	"core-js": "^2.6.5",
	"express": "^4.17.1",
	"iview": "^4.0.0-rc.4",
	"jsonwebtoken": "^8.5.1",
	"vue": "^2.6.10",
	"vue-router": "^3.0.3",
	"vuex": "^3.0.1"
},
複製代碼

server.js文件中配置跨域,並書寫測試接口:

let express = require('express')
let bodyParser = require('body-parser')

let app = express()

// 配置跨域
app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT"),
    res.header("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization")
    if (req.method.toLowerCase() === "options") {
        return res.end();
    }
    next();
})

// 配置bodyparser
app.use(bodyParser.json())

app.get("/user", (req, res) => {
    //在請求數據時,要加一個動畫,爲了測試,因此讓它時間長點,加了一個定時器
    setTimeout(() => {
        res.json({
            name: "wangcai"
        })
    }, 500)
})

app.listen(3000)
複製代碼

router.js中配置路由:

routes: [
	{
	  path: '/',
	  name: 'home',
	  component: Home
	},
	{
	  path: '/login',
	  name: 'login',
	  component: () => import('./views/Login.vue')
	},
	{
	  path: '/profile',
	  name: 'profile',
	  component: () => import('./views/Profile.vue')
	}
]
複製代碼

由於項目中須要用到樣式什麼的,這裏我引入了iViewmain.js代碼:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

//引入iView
import iView from 'iview'
import 'iview/dist/styles/iview.css';
Vue.use(iView)

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
複製代碼

由於項目中要用到加載數據的動畫,因此須要在store.js中的state中配置:

state: {
	//定義動畫是否顯示
	isShowLoading:false,
	username:'wangcai'
	},
	mutations: {
	//使動畫顯示
	showLoading(state){
	  state.isShowLoading = true;
	},
	//使動畫隱藏
	hideLoading(state){
	  state.isShowLoading = false;
	}
},
複製代碼

App.vue中配置跳轉:

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/login">Login</router-link> |
      <router-link to="/profile">Profile</router-link>
    </div>
    <router-view/>
  </div>
</template>
複製代碼

Home.vue代碼:

<template>
  <div class="home">
    <h1>首頁面</h1>
  </div>
</template>
複製代碼

Login.vue代碼:

<template>
    <div>
        <i-input placeholder="請輸入用戶名..." style="width: 300px"></i-input>
        <i-button type="primary">登陸</i-button>
    </div>
</template>
複製代碼

Profile.vue代碼:

<template>
<div>
    <h1>我的中心</h1>
</div>
</template>
複製代碼

而後在libs文件夾下面新建一個ajaxRequest.js文件,用來封裝咱們本身的 axios 和 加載動畫 等

import axios from 'axios'
import store from '../store'

//當第一次請求時,顯示loading  
class AjaxRequest {
    //當new的時候,調用這個方法
    constructor() {
        //請求的基礎路徑
        this.baseURL = process.env.NODE_ENV == "production" ? "/" : "http://localhost:3000"
        this.timeout = 3000 //超時時間
        this.queue = {} //存放每一次的請求
    }
    //定義一個方法,把options展開
    merge(options) {
        return {
            ...options,
            baseURL: this.baseURL,
            timeout: this.timeout
        }
    }
    //封裝一個攔截方法
    setInterceptor(instance, url) {
        //請求攔截,每次請求時,都要加上一個loading效果
        instance.interceptors.request.use((config) => {
            //每次請求時,都給他加一個Authorization頭,在JWT驗證時要用
            config.headers.Authorization = 'xxx'
            //第一次請求時,顯示loading動畫
            if (Object.keys(this.queue).length === 0) {
                store.commit('showLoading')
            }
            this.queue[url] = url;
            return config
        })
        //響應攔截  
        instance.interceptors.response.use((res) => {
            //刪除queue裏面的連接,若是同一個按鈕,你一秒以內點擊無數次,可是他只處理第一次操做
            delete this.queue[url]
            //隱藏loading動畫
            if (Object.keys(this.queue).length === 0) {
                store.commit('hideLoading')
            }
            //返回的結果
            return res.data
        })
    }
    request(options) {
        let instance = axios.create()   //建立一個axios實例
        this.setInterceptor(instance, options.url) //設置攔截
        let config = this.merge(options)
        return instance(config)     //axios執行後,返回promise
    }
}

export default new AjaxRequest;
複製代碼

而後在api文件夾下新建一個user.js文件用來放用戶相關的調用接口的方法(當你想要調用接口的時候,直接調用裏面的方法就好):

import axios from '../libs/ajaxRequset'

// 用戶相關的接口
export const getUser = ()=>{
    return axios.request({
        url:'/user',
        method:'get'
    })
}
複製代碼

修改Home.vue中的代碼:

<template>
  <div class="home">
    <h1>首頁面</h1>
    <p>當前登陸的用戶名是{{$store.state.username}}</p>
  </div>
</template>

<script>
//若是用export導出的話,要用這種形式,至關於解構賦值
import {getUser} from '../api/user'
export default {
  name:'home',
  async mounted(){
    let r = await getUser()
    console.log(r);
  }
}
</script>
複製代碼

修改App.vue中的代碼(加動畫效果):

<template>
  <div id="app">
    <Spin size="large" fix v-if="$store.state.isShowLoading">
      加載中...
    </Spin>
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/login">Login</router-link> |
      <router-link to="/profile">Profile</router-link>
    </div>
    <router-view/>
  </div>
</template>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>
複製代碼

運行效果:

圖片加載失敗!

使用JWT

server.js中新增代碼,使用JWT,並寫好登陸和驗證的接口:

let jwt = require('jsonwebtoken')

let secret = "xwc"

//登陸的接口
app.post('/login',(req,res)=>{
    let {username} = req.body;
    if(username === 'Fan'){
        //登陸成功後返回一個token
        res.json({
            code:0,
            username:'Fan',
            token:jwt.sign({username:'Fan'},secret,{
                expiresIn:20    //表示token20秒過時
            })
        })
    }else{
        //登陸失敗
        res.json({
            code:1,
            data:'登陸失敗了'
        })
    }
})

//驗證token的接口
app.get('/validate',(req,res)=>{
    let token = req.headers.authorization;  //咱們會把token放到咱們本身設置的http的頭authorization中,在這裏能夠直接拿到
    jwt.verify(token,secret,(err,decode)=>{     //驗證token
        if(err){
            return res.json({
                code:1,
                data:'token失效了'
            })
        }else{
            // token合法  在這裏,須要把token的時效延長,
            //總不能咱們看着看着忽然讓咱們從新登陸,token過時的意思是,你在這之間不進行任何操做纔會過時
            res.json({
                code:0,
                username:decode.username,
                token:jwt.sign({username:'Fan'},secret,{    //合法時,咱們須要從新生成一個token,咱們每次切換路由,都要從新生成一個token
                    expiresIn:20
                })
            })
        }
    })
})
複製代碼

接着在api文件夾下的user.js文件中添加登陸和驗證的方法:

import axios from '../libs/ajaxRequest'

// 用戶相關的接口

// 調獲取用戶信息的接口  向外暴露一個getUser方法  這個方法中調了接口
// 在組件中,就可使用getUser,就至關於調用接口
export const getUser = ()=>{
    return axios.request({
        url:'/user',
        method:'get'
    })
}

// 再向外暴露一個登陸的方法,方法內部也是調接口
// 在登陸組件中就能夠調用Login方法,須要給方法傳遞一個用戶名
export const login = (username)=>{
    return axios.request({
        url:'/login',
        method:'post',
        data:{
            username
        }
    })
}

//驗證token方法  
export const validate = ()=>{
    return axios.request({
        url:'/validate',
        method:'get'
    })
}
複製代碼

接着咱們在lib文件夾下新建一個local.js文件,用來設置或者獲取localStorage裏的token

//把得到到的token存到localStorage裏
export const setLocal = (key,value)=>{
    if(typeof value == 'object'){   //若是傳過來的是對象,則轉換成字符串
        value = JSON.stringify(value)
    }
    localStorage.setItem(key,value)     //存到localStorage裏
}

//獲取localStorage裏的token
export const getLocal = (key)=>{
    return localStorage.getItem(key)
}
複製代碼

而後修改store.js中的代碼:

import Vue from 'vue'
import Vuex from 'vuex'
import {login,validate} from './api/user'   //必須用這種方式引入
import {setLocal} from './libs/local'   //引入lib文件夾下的local.js文件中的setLocal方法(往localStorage裏存放token)

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    //定義動畫是否顯示
    isShowLoading:false,
    username:'wangcai'
  },
  mutations: {
    //使動畫顯示
    showLoading(state){
      state.isShowLoading = true;
    },
    //使動畫隱藏
    hideLoading(state){
      state.isShowLoading = false;
    },
    //定義修改用戶名的方法
    setUser(state,username){
      state.username = username
    }
  },
  // actions存放接口的調用  dispatch actions裏面放方法
  actions: {
    //這裏面全部的方法都是異步的

    //登陸方法
    async toLogin({commit},username){
      let r = await login(username) //調用user.js中的login方法,也就是調用登陸接口
      // console.log(r);
      if(r.code === 0){   //登陸成功後會給你返回json數據,裏面有code
        //登陸成功了
        commit('setUser',r.username)  //修改用戶名
        setLocal('token',r.token)   //把獲得的token存到localStorage裏
      }else{
        // console.log('............');
        return Promise.reject(r.data);  //若是失敗,返回一個promise失敗態
      }
    },

    //驗證token方法
    async validate({commit}){
      let r = await validate(); //調用user.js中的validate方法,也就是調用驗證接口
      if(r.code === 0){
        commit('setUser',r.username)
        setLocal('token',r.token) //咱們說了,驗證經過,或者每次切換路由時,都要從新生成token
      }
      return r.code === 0;  //返回token是否失效,true或者false
    }
  }
})
複製代碼

修改Login.vue中的代碼:

<template>
    <div>
        <i-input v-model="username" placeholder="請輸入用戶名..." style="width: 300px"></i-input>
        <i-button type="primary" @click="login()">登陸</i-button>
    </div>
</template>

<script>
import {mapActions} from 'vuex' //使用vuex中的mapActions方法,不會的請參考個人文章vuex的使用方法
export default {
    data(){
        return{
            username:''  //定義一個用戶名  
        }
    },
    methods:{
        ...mapActions(['toLogin']), //獲取store.js文件中的actions中的toLogin方法
        login(){
            // console.log(this['toLogin'](this.username));
            //使用獲取到的toLogin方法
            this['toLogin'](this.username).then(data=>{ //由於toLogin返回的是一個Promise,因此能夠.then
                this.$router.push('/')  //登陸成功,跳到首頁面
            },err=>{
                this.$Message.error(err)
            })
        }
    }
}
</script>
複製代碼

別忘了修改ajaxRequest.js文件,在請求攔截的時候,須要加個頭,前面咱們寫死了,這裏,要把token給他,而後每次路由跳轉訪問頁面的時候,都會帶上這個頭,用來驗證:

import {getLocal} from "../libs/local"	//引入

//請求攔截,每次請求時,都要加上一個loading效果
instance.interceptors.request.use((config) => {
	//每次請求時,都給他加一個Authorization頭,在JWT驗證時要用
	config.headers.Authorization = getLocal('token')
	//第一次請求時,顯示loading動畫
	if (Object.keys(this.queue).length === 0) {
		store.commit('showLoading')
	}
	this.queue[url] = url;
	return config
})
複製代碼

接着在router.js中設置路由:
哪一個頁面須要登陸後才能訪問的話,給這個路由添加meta,假如個人 我的中心頁面 須要登陸後才能訪問,那麼我須要修改代碼:

{
	path: '/profile',
	name: 'profile',
	component: () => import('./views/Profile.vue'),
	meta:{
		needLogin:true
	}
}
複製代碼

最後修改main.js中的代碼,當切換路由時,進行驗證:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

//引入iView
import iView from 'iview'
import 'iview/dist/styles/iview.css';

Vue.use(iView)

Vue.config.productionTip = false

//每一次切換路由時,都執行這個導航守衛
router.beforeEach(async (to,from,next)=>{
  let isLogin = await store.dispatch('validate')  //判斷是否登陸了
  // needLogin  表示哪些路由須要在登陸條件下才能訪問
  console.log(to);
  let needLogin = to.matched.some(match=>match.meta.needLogin)
  if(needLogin){
    //須要登陸
    if(isLogin){
      //登陸過了
      next()
    }else{
      //沒有登陸
      next('/login')
    }
  }else{
    //不須要登陸
    if(isLogin && to.path === '/login'){  //若是你訪問login頁面,則給你跳到首頁面,由於不須要登陸
      next('/')
    }else{
      next()
    }
  }
})

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
複製代碼

有關須要注意的點,都加註釋了,好好看註釋就行

至此,整個案例就結束了

源碼

點我獲取源碼


T_T

相關文章
相關標籤/搜索