利用Vue.js實現登錄/登出以及JWT認證


JSON Web Token 入門教程:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

后端代碼地址:https://github.com/lison16/vue-cource/commit/c0341e8ea79d876ae552281ad8c1f1da5049434f

代碼結構:

 

uer.js:封裝了登錄和授權兩個接口。

import axios from "./index";

export const getUserInfo = ({ userId }) => {
  debugger;
  return axios.request({
    url: "/getUserInfo",
    method: "post",
    data: {
      userId
    }
  });
};

export const login = ({ userName, password }) => {
  return axios.request({
    url: "/index/login",
    method: "post",
    data: {
      userName,
      password
    }
  });
};

export const authorization = () => {
  return axios.request({
    url: "/users/authorization",
    method: "get"
  });
};

 /src/store/module/user.js:由於是異步操作,所以都放在了actions中。

import { login, authorization } from "@/api/user";
import { setToken } from "@/lib/util";

const state = {
  //
  userName: "Caoqi"
};
const getters = {
  firstLetter: state => {
    return state.userName.substr(0, 1);
  }
};
const mutations = {
  //
  SET_USER_NAME(state, params) {
    state.userName = params.userName;
  }
};
const actions = {
  login({ commit }, { userName, password }) {
    return new Promise((resolve, reject) => {
      login({ userName, password })
        .then(res => {
          debugger;
          if (res.data.code === 200 && res.data.data.token) {
            setToken(res.data.data.token);
            resolve();
          } else {
            reject(new Error("錯誤"));
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  },
  authorization({ commit }, token) {
    return new Promise((resolve, reject) => {
      authorization()
        .then(res => {
          if (parseInt(res.data.code) === 401) {
            reject(new Error("token error"));
          } else {
            setToken(res.data.data.token);
            resolve();
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  },
  logout() {
    setToken("");
  }
};
export default {
  //namespaced:true,//有利於模塊更加密閉,不受外界的干擾
  state,
  getters,
  mutations,
  actions
};

 /src/lib/axios.js:

import axios from "axios";
import { baseURL } from "@/config";
import { getToken } from '@/lib/util';
//ES6類的寫法
class HttpRequest {
  //ES6默認參數
  constructor(baseUrl = baseURL) {
    this.baseUrl = baseUrl;
    this.queue = {}; //將請求都放到隊列中
  }
  getInsideConfig() {
    const config = {
      baseURL: this.baseUrl,
      hearders: {
        //
      }
    };
    return config;
  }
  //封裝攔截器
  interceptors(instance, url) {
    instance.interceptors.request.use(
      config => {
        //添加全局的loading
        //Object.keys() 方法會返回一個由一個給定對象的自身可枚舉屬性組成的數組
        if (!Object.keys(this.queue).length) {
          //spin.show
        }
        this.queue[url] = true;
        config.headers['Authorization'] = getToken(); return config;
      },
      error => {
        return Promise.reject(error);
      }
    );
    instance.interceptors.response.use(
      res => {
        delete this.queue[url];
        const { data, status } = res;
        return { data, status };
      },
      error => {
        delete this.queue[url];
        return Promise.reject(error);
      }
    );
  }

  request(options) {
    const instance = axios.create();
    /**
     * Object.assign() 方法用於將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象。
     * const target = { a: 1, b: 2 };
     * const source = { b: 4, c: 5 };
     * const returnedTarget = Object.assign(target, source);
     * console.log(target);
     * expected output: Object { a: 1, b: 4, c: 5 }
     */
    options = Object.assign(this.getInsideConfig(), options);
    this.interceptors(instance, options.url);
    return instance(options);
  }
}

export default HttpRequest;

 login.vue:

<template>
  <div>
    <div>
      <input v-model="userName">
    </div>
    <div>
      <input type="password" v-model="password">
    </div>
    <div>
      <button @click="handleSubmit">登錄</button>
    </div>
  </div>
</template>
<script>
import { mapActions } from "vuex";
export default {
  name: "login_page",
  data() {
    return {
      userName: "",
      password: ""
    };
  },
  methods: {
    ...mapActions(["login"]),
    handleSubmit() {
      this.login({
        userName: this.userName,
        password: this.password
      })
        .then(() => {
          console.log("success!!");
          this.$router.push({
            name: "home"
          });
        })
        .catch(error => {
          console.log(error);
        });
    }
  }
};
</script>

 /src/router/index.js:跳轉到任何頁面前都要驗證token

import Vue from "vue";
import Router from "vue-router";
import routes from "./router";
import store from "@/store";
import { setTitle, setToken, getToken } from "@/lib/util";

Vue.use(Router);

const router = new Router({
  routes
});

const HAS_LOGINED = false;
//全局前置守衛
/*
to: Route: 即將要進入的目標 路由對象
from: Route: 當前導航正要離開的路由
next: Function: 一定要調用該方法來 resolve 這個鈎子
 */
//模擬登陸驗證邏輯:當跳轉頁面為登陸頁面且已經登陸時,直接跳轉到home頁面,如果跳轉頁面不為登錄頁且已經登陸,則繼續執行,否則直接跳轉到登錄頁
router.beforeEach((to, from, next) => {
  to.meta && setTitle(to.meta.title);
  debugger
  const token = getToken();
  if (token) {
    store
      .dispatch("authorization", token)
      .then(() => {
        if (to.name == "login") {
          next({ name: "home" });
        } else {
          next();
        }
      })
      .catch(() => {
        setToken("");
        next({ name: "login" });
      });
  } else {
    if (to.name === "login") next();
    else next({ name: "login" });
  }
});

export default router;

 src/views/Home.vue:包含登出效果:清除cookie,頁面跳轉到login.vue

  <template>
  <div class="home">
    <b>{{ food }}</b>
    <button @click="handleClick('back')">返回上一頁</button>
    <button @click="handleClick('push')">跳轉到parent</button>
    <button @click="handleClick('replace')">替換到parent</button>
    <button @click="getInfo">請求數據</button>
    <button @click="handleLogout">退出登錄</button>
  </div>
</template>

  <script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";
import { getUserInfo } from '@/api/user'
import { mapActions } from 'vuex'
export default {
  name: "home",
  components: {
    HelloWorld
  },
  props: {
    food: {
      type: String,
      default: "apple"
    }
  },
  beforeRouteEnter(to, from, next) {
    // 在渲染該組件的對應路由被 confirm 前調用
    // 不!能!獲取組件實例 `this`
    // 因為當守衛執行前,組件實例還沒被創建
    next(vm => {
      //若想使用實例,可使用這種方法
      console.log(vm);
    });
  },
  beforeRouteLeave(to, from, next) {
    // const leave = confirm('您確定要離開嗎?')
    // if (leave) next()
    // else next(false)
    next();
  },
  methods: {
    ...mapActions([
      'logout'
    ]),
    handleClick(type) {
      if (type === "back") {
        //this.$router.back();
        this.$router.go(-1);
      } else if (type === "push") {
        const name = "caoqi";
        //使用push會在瀏覽器中加入一個記錄
        //使用路徑跳轉
        //this.$router.push("/parent");
        //還可以使用命名路由的方式:
        this.$router.push({
          // name: "parent",
          // //加入name參數,http://localhost:8080/#/parent?name=caoqi
          // query: {
          //   name: 'caoqi'
          // }

          // name: "argu",
          // //加入name參數,http://localhost:8080/#/argu/caoqi
          // params: {
          //   name: 'caoqi'
          // }

          //ES6寫法:
          path: `/argu/${name}`
        });
      } else if (type === "replace") {
        //使用replace不會在瀏覽歷史中加入記錄
        this.$router.replace({
          name: "parent"
        });
      }
    },
    getInfo() {
      getUserInfo({ userId: 21 }).then(res => {
        console.log("res: ", res);
      });
    },
    handleLogout () { this.logout() this.$router.push({ name: 'login' }) }
  }
};
</script>

 效果圖:(一開始登錄失敗,因為后台設置了密碼為123,登錄成功后,如果在cookie有效的時間段內,登錄系統其它頁面則無需登錄,若把cookie清除則再查看系統其它頁面,則直接跳轉到登錄頁進行登錄操作)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM