從壹開始先後端分離 [ Vue2.0+.NET Core2.1] 二十四║ Vuex + JWT 實現受權驗證登陸

壹週迴顧

哈嘍,又是元氣滿滿的一個週一,又與你們見面了,週末就是團圓節了,正好我們的先後端也要團圓了,爲何這麼說呢,由於之後的開發可能就須要先後端一塊兒了,兩邊也終於會師了,還有幾天Vue系列就基本告一段落了,你們也好好加油鴨,今天將的內容呢,其實細心的你看到題目應該就能你們猜到了,前提是一直看本系列的小夥伴們,包括以前.net core部分,這裏先簡單說下上週我們都說了什麼:css

週一:《十九║Vue基礎: 樣式動態綁定+生命週期》重點說了下 Vue 開發中的八個生命週期,這個是一個重點,但願你們能夠多看看,這個在之後的開發中會常常遇到;html

週二:《二十║Vue基礎終篇:組件詳解+項目說明》重點說了下組件的使用,包括定義、傳值、使用等等,這個更是重中之重,組件的使用在 Vue 的開發中必不可少;前端

週三:《二十一║Vue實戰:開發環境搭建【詳細版】》詳細的說了下開發環境的搭建,不只講了如何搭建,還詳細的說明了每個工具、插件的使用意義;vue

週四:《二十二║Vue實戰:我的博客初版(axios+router)》根據週三搭建的環境,第一次建立了我們初版的我的博客,封裝了 axios ,第一次鏈接上了我們以前的 .net core api;node

週五:《二十三║Vue實戰:Vuex 其實很簡單》經過一個小 DEMO 說明了 Vuex 是如何對咱們的 Vue 實行狀態化管理的,讓你們對其使用有了必定的瞭解,爲在之後的大項目中使用打下基礎;webpack

週五的時候,我們經過對錶單的組件化,來講明瞭 vuex 的存在乎義,今天我們仍是會用到這個 vuex ,並且還會配合着 .net core api,究竟是什麼呢?請看今天的講解。ios

注意:週四的時候,只寫了我的博客的首頁,週末的時候,已經把詳情頁更新了,你們能夠自行去 Git 查看,文末有地址git

 

 

零、今天要完成右下角粉色區塊的部分

 

 

1、如何實現權限驗證的過程

你們必定還記得以前在 .net core api 系列文章中《框架之五 || Swagger的使用 3.3 JWT權限驗證【修改】》,我們經過對 JWT 的講解,實現了對接口的驗證,你們能夠去了解一下,當時由於是沒有前端,因此我們就直接用的 Swagger 接口文檔來手動設置的權限驗證,當時羣裏有不少小夥伴對這個不是很明白,我也是簡單說了下,經過手動在 swagger 中輸入Header ,變成每次 vue 的 axios 請求的 Header 中添加 Token,這個 Token 就是我們手動配置的那個,由於當時沒有先後端搭配,因此只是比較籠統的說了下這個流程,今天呢,就重點來講下這個受權登陸驗證,也爲下邊的管理後臺鋪路,這裏配合 Vue 前端,再仔細講講是如何實現先後端同時驗證的:github

 

上圖中說的也是很詳細了,主要分爲兩個驗證:web

一、前端驗證(藍色部分),用戶訪問一個頁面,首先判斷是否須要驗證登陸,好比管理後臺,或者訂單系統的下單頁(首頁和詳情頁天然是不須要用戶登陸的,購物車和訂單等必須登陸,固然有些遊客也能夠購買的除外),而後去驗證是否存在 Token,存在就添加到 axios 的 Header 中去請求後端 API,反之則去登陸頁登陸;

二、後端驗證(綠色部分),這個就是我們以前在說 .net core api 的時候說到的 JWT 受權驗證,根據當前前端 axios 請求中傳來的 Token ,解析出是否被篡改,以及是否會相應的權限,這樣就能夠進一步返回數據了;

這個時候你們必定會有疑惑了,既然如今每個接口都定義了權限,爲何要倆邊都須要驗證,只須要後端 api 一個驗證不就好了,何須這麼麻煩?我認爲是這樣的:

首先前端驗證的主要目的是:經過手動配置,可讓用戶去主動獲取 Token ,不用每次都去獲取,並且也減輕了後端請求的次數,總不能是先去發送請求,再判斷當前頁面是否須要登陸吧,嗯,總結來講,

前端是爲了頁面級登陸,後端是爲了接口級驗證,並且也是想把 vue 前端工程化的思想。

 

2、結合API設計登陸頁 —— 實現後端驗證

一、引入 ElementUI 樣式框架

 由於以後須要一個管理後臺,因此考慮着增長一個框架,目前比較流行的就是 ElementUI 和 IView,今天我們先說一下引用 ElementUI

首先,在項目中 執行 npm install,初始化之後,在 node_modules 中查看是否存在 element-ui 文件夾,若是沒有,則執行

npm i element-ui -S

而後就能夠看到項目中已經成功安裝 elementui 了

 

而後、在項目的入口配置文件 main.js 中,引用

import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI)

若是項目沒有報錯,到此則安裝成功。 

 

二、添加統一登陸頁面

第1、在 src 的 views 文件夾內,添加 Login.vue 頁面,並添加內容:

<template>
    <el-row type="flex" justify="center">
        <el-form ref="loginForm" :model="user" :rules="rules" status-icon label-width="50px">
            <el-form-item label="帳號" prop="name">
                <el-input v-model="user.name"></el-input>
            </el-form-item>
            <el-form-item label="密碼" prop="pass">
                <el-input v-model="user.pass" type="password"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" icon="el-icon-upload" @click="login">登陸</el-button>
            </el-form-item>
        </el-form>
    </el-row>
</template>

<script> export default { methods: { login() {//使用elementui validate驗證
      this.$refs.loginForm.validate(valid => { if (valid) {//這裏在下邊會改寫成登陸信息 感謝 @風格不一樣 提醒註釋錯誤問題 if (this.user.name === "admin" && this.user.pass === "123") { this.$notify({ type: "success", message: "歡迎你," + this.user.name + "!", duration: 3000 }); this.$router.replace("/"); } else { this.$message({ type: "error", message: "用戶名或密碼錯誤", showClose: true }); } } else { return false; } }); } }, data() { return { user: {},//配合頁面內的 prop 定義數據
      rules: {//配合頁面內的 prop 定義規則
        name: [{ required: true, message: "用戶名不能爲空", trigger: "blur" }], pass: [{ required: true, message: "密碼不能爲空", trigger: "blur" }] } }; } }; </script>

添加路由後,測試頁面是否可行

 

三、配合後臺登陸請求

完善  BlogController.cs 頁面,稍微調整了下接口,和以前的沒有差異,並增長權限驗證

 

 /// <summary>
        /// 獲取博客列表 /// </summary>
        /// <param name="id"></param>
        /// <param name="page"></param>
        /// <param name="bcategory"></param>
        /// <returns></returns>
 [HttpGet] public async Task<object> Get(int id, int page = 1, string bcategory = "技術博文") { int intTotalCount = 6; int TotalCount = 1; List<BlogArticle> blogArticleList = new List<BlogArticle>(); if (redisCacheManager.Get<object>("Redis.Blog") != null) { blogArticleList = redisCacheManager.Get<List<BlogArticle>>("Redis.Blog"); } else { blogArticleList = await blogArticleServices.Query(a => a.bcategory == bcategory); redisCacheManager.Set("Redis.Blog", blogArticleList, TimeSpan.FromHours(2)); } TotalCount = blogArticleList.Count() / intTotalCount; blogArticleList = blogArticleList.OrderByDescending(d => d.bID).Skip((page - 1) * intTotalCount).Take(intTotalCount).ToList(); foreach (var item in blogArticleList) { if (!string.IsNullOrEmpty(item.bcontent)) {
                    int totalLength = 500; if (item.bcontent.Length > totalLength) { item.bcontent = item.bcontent.Substring(0, totalLength); } } } var data = new { success = true, page = page, pageCount = TotalCount, data = blogArticleList }; return data; } // GET: api/Blog/5
        /// <summary>
        /// 獲取詳情 /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet("{id}", Name = "Get")] public async Task<object> Get(int id) { var model = await blogArticleServices.getBlogDetails(id); var data = new { success = true, data = model }; return data; }

 

調整 LoginController.cs 的獲取 Token 方法:

/// <summary>
        /// 獲取JWT的方法 /// </summary>
        /// <param name="id">id</param>
        /// <param name="sub">角色</param>
        /// <returns></returns>
 [HttpGet] [Route("Token")] public JsonResult GetJWTStr(string name, string pass) { string jwtStr = string.Empty; bool suc = false; //這裏就是用戶登陸之後,經過數據庫去調取數據,分配權限的操做 //這裏直接寫死了
            if (name == "admins" && pass == "admins") { TokenModelJWT tokenModel = new TokenModelJWT(); tokenModel.Uid = 1; tokenModel.Role = "Admin"; jwtStr = JwtHelper.IssueJWT(tokenModel); suc = true; } else { jwtStr = "login fail!!!"; } var result = new { data = new { success = suc, token = jwtStr } }; return Json(result); }

 

四、修改 前端的 Login.vue 頁面的登陸方法,獲取到 Token ,並把其保存到 Vuex 中

 

<template>
    <el-row type="flex" justify="center">
        <el-card v-if="isLogin"> 歡迎:admins <br>
            <br>
            <el-button type="primary" icon="el-icon-upload" @click="loginOut">退出登陸</el-button>
        </el-card>
        <el-form v-else ref="loginForm" :model="user" :rules="rules" status-icon label-width="50px">
            <el-form-item label="帳號" prop="name">
                <el-input v-model="user.name"></el-input>
            </el-form-item>
            <el-form-item label="密碼" prop="pass">
                <el-input v-model="user.pass" type="password"></el-input>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" icon="el-icon-upload" @click="login">登陸</el-button>
            </el-form-item>
        </el-form>
    </el-row>
</template>

<script> export default { methods: { login: function() { let that = this; that.$store.commit("saveToken", "");//清掉 token this.$refs.loginForm.validate(valid => { if (valid) { this.$api.get( "Login/Token", { name: that.user.name, pass: that.user.pass }, r => { if (r.data.success) { var token = r.data.token; that.$store.commit("saveToken", token);//保存 token this.$notify({ type: "success", message: "歡迎你," + this.user.name + "!", duration: 3000 }); console.log(that.$store.state.token); this.$router.replace("/"); } else { this.$message({ type: "error", message: "用戶名或密碼錯誤", showClose: true }); } } ); } else { return false; } }); }, loginOut(){ this.isLogin=false; this.$store.commit("saveToken", "");//清掉 token } }, data() { return { isLogin:false, user: {}, rules: { name: [{ required: true, message: "用戶名不能爲空", trigger: "blur" }], pass: [{ required: true, message: "密碼不能爲空", trigger: "blur" }] } }; }, created() { if (window.localStorage.Token&&window.localStorage.Token.length>=128){ this.isLogin=true; } } }; </script>

 

五、修改 vuex 倉庫,把 token 存進store中

import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ // 初始化的數據
 state: { formDatas: null, token: "1" }, // 改變state裏面的值得方法
 mutations: { getFormData(state, data) { state.formDatas = data; }, saveToken(state, data) { state.token = data; window.localStorage.setItem("Token", data);//就是這裏,保存到了 localStorage 中
 } } }); // 輸出模塊
export default store;

 

六、這個時候要修改下以前咱們封裝的 http.js 方法,由於當時咱們過濾掉了失敗的方法,這裏要打開下,你們自行修改下

這個時候,咱們再登陸的話,已經發生變化

 

這個時候你們能夠看到,咱們成功的登陸了(右上角有歡迎提示),而後 token 也成功的保存到 stroe/localStorage 裏(下邊控制檯輸出),

由於咱們在博客頁增長了權限,雖然咱們是用的 admin 帳號,可是 Header 中尚未添加Token,因此如今仍是 401,那如何纔能有效的增長請求 Header 呢,請往下看,權限驗證前端部分。

 

 

3、實現一:登陸攔截驗證——路由攔截

 一、修改 router.js 路由,實現按需登陸

在須要登陸的地方,增長登陸要求字段,

而後增長 beforeEach 鉤子函數(這裏有一個問題,只能獲取到本地緩存數據,沒法獲取 Vuex ,正在研究中)

import Vue from "vue"; import Router from "vue-router"; import Home from "./views/Home.vue"; import FormVuex from "./views/FormVuex.vue"; import Content from "./views/content"; import Login from "./views/Login"; import store from "./store"; Vue.use(Router); const router = new Router({ mode: "history", base: process.env.BASE_URL, routes: [ { path: "/", name: "home", component: Home, meta: { requireAuth: true // 添加該字段,表示進入這個路由是須要登陸的
 } }, { path: "/Vuex", name: "Vuex", component: FormVuex }, { path: "/Content/:id", name: "Content", component: Content }, { path: "/Login", name: "Login", component: Login }, { path: "/about", name: "about", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ "./views/Form.vue") } ] }); router.beforeEach((to, from, next) => { if (to.meta.requireAuth) {  // 判斷該路由是否須要登陸權限
        if (window.localStorage.Token&&window.localStorage.Token.length>=128) {  // 經過vuex state獲取當前的token是否存在
 next(); } else { next({ path: '/login', query: {redirect: to.fullPath} // 將跳轉的路由path做爲參數,登陸成功後跳轉到該路由
 }) } } else { next(); } }) export default router;

 二、修改 http.js 封裝方法,自動在請求中把 Token 添加到 Header 中

上邊的路由設置,僅僅是對 Token 進行判斷,尚未添加到 Header 裏,更沒有進行驗證

注意:目前是用的 localStorage 本地存儲的方法(這一點是受到微信小程序的啓發),

可是直接在 router.js 中直接獲取 store 的token屬性,取不到,有知道的小夥伴請留言 

更新:通過羣裏小夥伴的提醒 還有樓下評論席@路遙心安 的提醒,這裏不是取不到,而是必須用 localstoreage 來賦值,由於 vuex 只是一個分發管理狀態的做用,並無本地保存的功能,

  

 

import store from "../store"; import router from "../router.js"; // 配置API接口地址
var root = "http://localhost:58427/api"; var root1 = "http://apk.neters.club/api"; // 引用axios
var axios = require("axios"); // 自定義判斷元素類型JS
function toType(obj) { return {}.toString .call(obj) .match(/\s([a-zA-Z]+)/)[1] .toLowerCase(); } // 參數過濾函數
function filterNull(o) { for (var key in o) { if (o[key] === null) { delete o[key]; } if (toType(o[key]) === "string") { o[key] = o[key].trim(); } else if (toType(o[key]) === "object") { o[key] = filterNull(o[key]); } else if (toType(o[key]) === "array") { o[key] = filterNull(o[key]); } } return o; } // http request 攔截器
axios.interceptors.request.use( config => { if (window.localStorage.Token&&window.localStorage.Token.length>=128) {//store.state.token 獲取不到值?? // 判斷是否存在token,若是存在的話,則每一個http header都加上token
      config.headers.Authorization ="Bearer "+ window.localStorage.Token; } return config; }, err => { return Promise.reject(err); } ); // http response 攔截器
axios.interceptors.response.use( response => { return response; }, error => { if (error.response) { switch (error.response.status) { case 401: // 返回 401 清除token信息並跳轉到登陸頁面
 router.replace({ path: "login", query: { redirect: router.currentRoute.fullPath } }); } } return Promise.reject(error.response.data); // 返回接口返回的錯誤信息
 } ); /* 接口處理函數 這個函數每一個項目都是不同的,我如今調整的是適用於 https://cnodejs.org/api/v1 的接口,若是是其餘接口 須要根據接口的參數進行調整。參考說明文檔地址: https://cnodejs.org/topic/5378720ed6e2d16149fa16bd 主要是,不一樣的接口的成功標識和失敗提示是不一致的。 另外,不一樣的項目的處理方法也是不一致的,這裏出錯就是簡單的alert */ function apiAxios(method, url, params, success, failure) { if (params) { params = filterNull(params); } axios({ method: method, url: url, data: method === "POST" || method === "PUT" ? params : null, params: method === "GET" || method === "DELETE" ? params : null, baseURL: root, withCredentials: false }) .then(function(res) { success(res.data); }) .catch(function(err) { let res = err.response; if (err) { window.alert("api error, HTTP CODE: " + res.status); } }); } // 返回在vue模板中的調用接口
export default { get: function(url, params, success, failure) { return apiAxios("GET", url, params, success, failure); }, post: function(url, params, success, failure) { return apiAxios("POST", url, params, success, failure); }, put: function(url, params, success, failure) { return apiAxios("PUT", url, params, success, failure); }, delete: function(url, params, success, failure) { return apiAxios("DELETE", url, params, success, failure); } };

運行項目查看:

 

你們觀察能夠看到,咱們第一次點擊 Home 的時候,發現跳轉到了 Login 頁面,而後登陸後,自動跳轉首頁,併成功獲取到數據,登陸成功!

而後退出登陸,發現首頁已經進不去了,退出成功!

 

4、說明

 今天由於時間的關係,沒有把 Vuex 在路由中如何獲取研究出來,這裏先用了本地緩存來代替了,你們若是有知道的小夥伴,請留言哈~~~不勝感激,

5、CODE

前端:
https://github.com/anjoy8/Blog.Vue

後端:

https://github.com/anjoy8/Blog.Core

相關文章
相關標籤/搜索