从壹开始先后端分离 [ 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

相关文章
相关标签/搜索