接上篇,这次是真的接上篇,针对上篇未完成的部分,增加鉴权功能,开始之前,我们先要介绍一个新的知识,路由元数据。

在vue-router中,定义元数据的方式:

const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})

那么如何访问这个 meta 字段呢?

首先,我们把routes 配置中的每个路由对象叫做路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录

例如,根据上面的路由配置,/foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。

一个路由匹配到的所有路由记录会暴露为 $route 对象(还有在导航钩子中的 route 对象)的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

所以在vue-router官方文档中,我们可以看到下面的代码,其实就是前端路由授权的粗糙实现方式(代码不做过多解释,里面我加入了详细的注释):

router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// 如果路由配置了元数据requiresAuth为true,则需要鉴权,这是需要判断是否登录
// 如果没有登录则跳转到login页面
if (!auth.loggedIn()) {
next({
path: '/login',
//这里传递fullPath,是为了登录之后作为return back
query: { redirect: to.fullPath }
})
} else {
//如果已经登录过,直接执行进入下一步
next()
}
} else {
//对没有配置requiresAuth的路由进行处理,如果不加入,则路由未配置requiresAuth,无法进入,所以确保一定要调用 next()
next()
}
})

好了,基础知识介绍完毕,现在我们把我们的路由加入meta信息,启用权限验证:

var router = new VueRouter({
routes: [{
name: 'home', path: '/home', component: HomeComponent
},
{
name: 'customers', path: '/customers', component: CustomerListComponent,
meta: {
auth: true
} },
{
name: 'detail', path: '/detail/:id', component: CustomerComponent,
meta: {
auth: true
} },
{
name: 'login', path: '/login', component: LoginComponent
}
]
});
//注册全局事件钩子
router.beforeEach(function (to, from, next) {
//如果路由中配置了meta auth信息,则需要判断用户是否登录;
if (to.matched.some(r => r.meta.auth)) {
//登录后会把token作为登录的标示,存在localStorage中
if (!localStorage.getItem('token')) {
console.log("需要登录");
next({
path: '/login',
query: { to: to.fullPath }
})
} else {
next();
}
} else {
next()
}
});

更新代码后,可以跟目录运行node app.js ,打开8110端口,查看,运行效果如下:

这个时候,无论从浏览器地址栏还是通过跳转方式,在点击配置了 meta:{auth:true}的路由时,如果没有登录,都会跳转到登录页面,并记录return back url。

下面我们加入登录逻辑,并修改后台接口,支持用户授权,后台我们使用jwt的一个实现https://github.com/auth0/node-jsonwebtoken ,直接使用npm 安装即可,对jwt不太了解的同学,可以搜索 json web token (jwt)(另外为了读取http body,我们这里会使用 body-parser,可以直接使用npm install --save body-parser 安装)。

首先修改我们的登录组件:

 methods: {

        login: function () {
var self = this;
axios.post('/login', this.user)
.then(function (res) {
console.log(res);
if (res.data.success) {
localStorage.setItem('token', res.data.token);
console.log(self.$router);
self.$router.push({
path: self.$route.query.to
});
} else {
alert(res.data.errorMessage);
}
})
.catch(function (error) {
console.log(error);
}); }
}

并添加全局拦截器,在任何ajax请求中加入token 头,如果熟悉angular拦截器的同学对axios实现的拦截器应该很熟悉的,这和jquery 对Ajax.setting的设置类似:

// request 拦截器 ,对所有请求,加入auth
axios.interceptors.request.use(
cfg => {
// 判断是否存在token,如果存在,则加上token
if (localStorage.getItem('token')) {
cfg.headers.Authorization = localStorage.getItem('token');
}
return cfg;
},
err => {
return Promise.reject(err);
}); // http response 拦截器
axios.interceptors.response.use(
res => {
return res;
},
err => {
if (err.response) {
switch (err.response.status) {
case 401: //如果未授权访问,则跳转到登录页面
router.replace({
path: '/login',
query: {redirect: router.currentRoute.fullPath}
})
}
}
return Promise.reject(err.response.data)
});

这样,我们再每次请求的时候,如果token存在,则就会带上token;

接着,修改我们的后端部分,加入处理登录,以及生成解析token的部分,修改我们的authMiddleware.js文件:

var jwt = require('jsonwebtoken');
/**
* 有效用户列表
*/
var validUsers = [{
username: 'zhangsan',
password: '123456'
}, {
username: 'lisi',
password: '123456'
}]; //FIXME:这个作为密钥,一定要安全的,这里我为了简单就直接写了一大段字符串
const secretKey = 'dhahr3uiudfu93u43i3uy43&*&$#*&437hjhfjdjhfdfjsy8&*&*JNFSJDJHH??>:LP'; /**
* 创建token
* @param {用户对象} user
*/
var createToken = function (user) {
/**
* 创建token 并设置过期时间为一个小时
*/
return jwt.sign({ data: user, exp: Math.floor(Date.now() / 1000) + (60 * 60) }, secretKey);
}
/**
* 解析token
* @param {用户需要验证的token} token
*/
var parseToken = function (token, callback) {
jwt.verify(token, secretKey, function (err, result) {
callback && callback(err, result);
});
} module.exports = function (req, res, next) {
//如果是登录请求
console.log(req.path);
if (req.path === "/login") {
var username = req.body.username;
var password = req.body.password;
//判断用户名和密码是否正确
var user = validUsers.filter(u => u.username === username && u.password === password)[0];
//如果用户用户名密码匹配成功,直接创建token并返回
if (user) {
res.json({
success: true,
token: createToken(user)
})
} else {
res.json({
success: false,
errorMessage: 'username or password is not correct,please retry again'
})
}
} else {//如果不是登录请求,则需要检查token 的合法性
var token = req.get('Authorization');
console.log(token);
if (token) {
parseToken(token, function (err, result) {
if (err) {//如果解析失败,则返回失败信息
res.status(401).json( {
success: false,
errorMessage: JSON.stringify(err)
})
} else {
next();
}
})
}else{
res.status(401).json({
success:false,
errorMessage:'未授权的访问'
}); } } }

上述代码加上注释应该没什么复杂度的,各位同学应该可以看的明白,这样之后,我们启用我们的授权中间件,修改/app.js文件:

var express = require("express");
var bodyParser = require("body-parser"); var authMiddleware = require('./middleware/authMiddleware');
var customerRouter = require('./router/customers'); var app = express(); app.use(express.static('public'));
app.get('/portal', function (req, res) {
res.json({
data: [
{
visits: 12,
clicks: 100
},
{
location: 'BeiJing',
total: 17
}
]
})
}) app.use(bodyParser.json())
app.use(authMiddleware); app.use('/api', customerRouter);

运行我们的代码可以看到如下效果:

博客园对图片大小有要求,不能很好的截取,就只截取了一部分,这是登录后的效果,登录前的效果,大家可以自己测试,完整代码如下:

/app.js

var express = require("express");
var bodyParser = require("body-parser"); var authMiddleware = require('./middleware/authMiddleware');
var customerRouter = require('./router/customers'); var app = express(); app.use(express.static('public'));
app.get('/portal', function (req, res) {
res.json({
data: [
{
visits: 12,
clicks: 100
},
{
location: 'BeiJing',
total: 17
}
]
})
}) app.use(bodyParser.json())
app.use(authMiddleware); app.use('/api', customerRouter); app.listen(8110, function () {
console.log("port 8110 is listenning!!!");
});

/public/app.js

var LoginComponent = {
template: ` <div class="login" >
username:<input type="text" v-model="user.username" />
password:<input type="password" v-model="user.password" />
<input type="button" @click="login()" value="login" />
</div>
`,
data: function () {
return {
user: {
username: '',
password: ''
}
}
},
methods: { login: function () {
var self = this;
axios.post('/login', this.user)
.then(function (res) {
console.log(res);
if (res.data.success) {
localStorage.setItem('token', res.data.token);
console.log(self.$router);
self.$router.push({
path: self.$route.query.to
});
} else {
alert(res.data.errorMessage);
}
})
.catch(function (error) {
console.log(error);
}); }
}
} var CustomerListComponent = {
template: `
<div>
<div>
<input type="text" v-model="keyword" /> <input type="button" @click="getCustomers()" value="search" />
</div>
<ul>
<router-link v-for="c in customers" tag="li" :to="{name:'detail',params:{id:c.id}}" :key="c.id">{{c.name}}</router-link>
</ul>
</div>
`,
data: function () {
return {
customers: [],
keyword: ''
}
},
created: function () {
this.getCustomers();
},
methods: {
getCustomers: function () {
axios.get('/api/getCustomers', { params: { keyword: this.keyword } })
.then(res => { this.customers = res.data; console.log(res) })
.catch(err => console.log(err));
}, }
} var CustomerComponent = {
template: `
<div>
{{customer}}
</div>
`,
data: function () {
return {
customer: {}
}
},
created: function () {
var id = this.$route.params.id;
this.getCustomerById(id);
},
watch: {
'$route': function () {
console.log(this.$route.params.id);
}
},
methods: {
getCustomerById: function (id) {
axios.get('/api/customer/' + id)
.then(res => this.customer = res.data)
.catch(err => console.log(err));
}
}
} var HomeComponent = {
template: `<div>
<h1>Home 页面,portal页</h1>
<h2>以下数据来自服务端</h2>
{{stat}}
</div>`,
data: function () {
return {
stat: ''//代表相关统计信息等
}
},
methods: {
getStat: function () {
return axios.get('/portal');
}
},
created: function () {
this.getStat().then(res => {
this.stat = JSON.stringify(res.data);
}).catch(err => {
console.log(err);
})
}
} var router = new VueRouter({
routes: [{
name: 'home', path: '/home', component: HomeComponent
},
{
name: 'customers', path: '/customers', component: CustomerListComponent,
meta: {
auth: true
} },
{
name: 'detail', path: '/detail/:id', component: CustomerComponent,
meta: {
auth: true
} },
{
name: 'login', path: '/login', component: LoginComponent
}
]
}); //注册全局事件钩子
router.beforeEach(function (to, from, next) {
//如果路由中配置了meta auth信息,则需要判断用户是否登录;
if (to.matched.some(r => r.meta.auth)) {
//登录后会把token作为登录的标示,存在localStorage中
if (!localStorage.getItem('token')) {
console.log("需要登录");
next({
path: '/login',
query: { to: to.fullPath }
})
} else {
next();
}
} else {
next()
}
}); // request 拦截器 ,对所有请求,加入auth
axios.interceptors.request.use(
cfg => {
// 判断是否存在token,如果存在,则加上token
if (localStorage.getItem('token')) {
cfg.headers.Authorization = localStorage.getItem('token');
}
return cfg;
},
err => {
return Promise.reject(err);
}); // http response 拦截器
axios.interceptors.response.use(
res => {
return res;
},
err => {
if (err.response) {
switch (err.response.status) {
case 401: //如果未授权访问,则跳转到登录页面
router.replace({
path: '/login',
query: {redirect: router.currentRoute.fullPath}
})
}
}
return Promise.reject(err.response.data)
}); var app = new Vue({
router: router,
template: `
<div>
<router-link :to="{name:'home'}" >Home</router-link>
<router-link :to="{name:'customers'}" >Customers</router-link>
<router-view></router-view>
</div>
`,
el: '#app'
});

/public/index.html

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>demo3</title>
<script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script>
<script src="https://cdn.bootcss.com/vue-router/2.7.0/vue-router.js"></script>
<script src="https://cdn.bootcss.com/axios/0.16.2/axios.js"></script> </head> <body>
<div id="app"> </div>
<script src="./app.js"></script>
</body> </html>

/router/customers.js

var router = require("express").Router();
var db = require('./fakeData'); router.get('/getCustomers', function (req, res) {
var list = db.data; list = list.filter(v => v.name.indexOf(req.query.keyword) !== -1); res.json(list);
}); router.get('/customer/:id',function(req,res){
var list=db.data; var obj=list.filter(v=>v.id==req.params.id)[0]; res.json(obj);
}) module.exports = router;

/router/fakeData.json

{
"data": [
{
"id":1,
"name": "zhangsan",
"age": 14,
"sexy": "男",
"majar": "学生"
},
{
"id":2,
"name": "lisi",
"age": 19,
"sexy": "女",
"majar": "学生"
},
{
"id":3,
"name": "wangwu",
"age": 42,
"sexy": "男",
"majar": "工人"
},
{
"id":4,
"name": "maliu",
"age": 10,
"sexy": "男",
"majar": "学生"
},
{
"id":5,
"name": "wangermazi",
"age": 82,
"sexy": "男",
"majar": "画家"
},
{
"id":6,
"name": "liudehua",
"age": 55,
"sexy": "男",
"majar": "天王"
},
{
"id":7,
"name": "zhoujielun",
"age": 14,
"sexy": "男",
"majar": "歌手"
},
{
"id":8,
"name": "wangfei",
"age": 50,
"sexy": "女",
"majar": "歌手"
},
{
"id":9,
"name": "mayun",
"age": 60,
"sexy": "男",
"majar": "老板"
}
]
}

/middleware/authMiddleware.js

var jwt = require('jsonwebtoken');
/**
* 有效用户列表
*/
var validUsers = [{
username: 'zhangsan',
password: '123456'
}, {
username: 'lisi',
password: '123456'
}]; //FIXME:这个作为密钥,一定要安全的,这里我为了简单就直接写了一大段字符串
const secretKey = 'dhahr3uiudfu93u43i3uy43&*&$#*&437hjhfjdjhfdfjsy8&*&*JNFSJDJHH??>:LP'; /**
* 创建token
* @param {用户对象} user
*/
var createToken = function (user) {
/**
* 创建token 并设置过期时间为一个小时
*/
return jwt.sign({ data: user, exp: Math.floor(Date.now() / 1000) + (60 * 60) }, secretKey);
}
/**
* 解析token
* @param {用户需要验证的token} token
*/
var parseToken = function (token, callback) {
jwt.verify(token, secretKey, function (err, result) {
callback && callback(err, result);
});
} module.exports = function (req, res, next) {
//如果是登录请求
console.log(req.path);
if (req.path === "/login") {
var username = req.body.username;
var password = req.body.password;
//判断用户名和密码是否正确
var user = validUsers.filter(u => u.username === username && u.password === password)[0];
//如果用户用户名密码匹配成功,直接创建token并返回
if (user) {
res.json({
success: true,
token: createToken(user)
})
} else {
res.json({
success: false,
errorMessage: 'username or password is not correct,please retry again'
})
}
} else {//如果不是登录请求,则需要检查token 的合法性
var token = req.get('Authorization');
console.log(token);
if (token) {
parseToken(token, function (err, result) {
if (err) {//如果解析失败,则返回失败信息
res.status(401).json( {
success: false,
errorMessage: JSON.stringify(err)
})
} else {
next();
}
})
}else{
res.status(401).json({
success:false,
errorMessage:'未授权的访问'
}); } } }

/package.json

{
"name": "vue_demo3",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.17.2",
"express": "^4.15.3",
"jsonwebtoken": "^7.4.1"
}
}

一步一步学Vue(九)的更多相关文章

  1. 一步一步学Vue(九) 路由元数据

    一步一步学Vue(九):https://www.cnblogs.com/Johnzhang/p/7260888.html

  2. 一步一步学Vue(六)https://www.cnblogs.com/Johnzhang/p/7242640.html

    一步一步学Vue(六):https://www.cnblogs.com/Johnzhang/p/7237065.html  路由 一步一步学Vue(七):https://www.cnblogs.com ...

  3. 一步一步学Vue(四)

    接上篇.上篇中给出了代码框架,没有具体实现,这一篇会对上篇定义的几个组件进行分别介绍和完善: 1.TodoContainer组件 TodoContainer组件,用来组织其它组件,这是react中推荐 ...

  4. 一步一步学Vue(十二)

    为了提升代码的逼格,之后代码改为Vue文件组件,之前代码虽然读起来容易理解,而且适合在小的项目中使用,但是有如下缺点: 全局定义(Global definitions) 强制要求每个 componen ...

  5. 一步一步学Vue(六)

    本篇继续介绍vue-router,我们需要要完成这样个demo:<分页显示文章列表>:这里我们以博客园首页列表为例简化处理: 按照上图框选所示,简单分为蓝色部分文章组件(ArticleIt ...

  6. (转载)一步一步学Linq to sql系列文章

    现在Linq to sql的资料还不是很多,本人水平有限,如果有错或者误导请指出,谢谢. 一步一步学Linq to sql(一):预备知识 一步一步学Linq to sql(二):DataContex ...

  7. 【DG】[三思笔记]一步一步学DataGuard

    [DG][三思笔记]一步一步学DataGuard 它有无数个名字,有人叫它dg,有人叫它数据卫士,有人叫它data guard,在oracle的各项特性中它有着举足轻理的地位,它就是(掌声)..... ...

  8. 一步一步学习Vue(十一)

    本篇继续学习vuex,还是以实例为主:我们以一步一步学Vue(四)中讲述的例子为基础,对其改造,基于vuex重构一遍,这是原始的代码: todolist.js ; (function () { var ...

  9. 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑

    阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...

  10. 一步一步学ROP之linux_x64篇

    一步一步学ROP之linux_x64篇 一.序 **ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防 ...

随机推荐

  1. caffe源码学习之Proto数据格式【1】

    前言: 由于业务需要,接触caffe已经有接近半年,一直忙着阅读各种论文,重现大大小小的模型. 期间也总结过一些caffe源码学习笔记,断断续续,这次打算系统的记录一下caffe源码学习笔记,巩固一下 ...

  2. 锁和监视器之间的区别 – Java并发

    在面试中你可能遇到过这样的问题:锁(lock)和监视器(monitor)有什么区别? 嗯,要回答这个问题,你必须深入理解Java的多线程底层是如何工作的. 简短的答案是,锁为实现监视器提供必要的支持. ...

  3. css3自适应圆

    .class{ width:auto; height:auto; border-radius:11px; min-width:14px; padding:0 4px; font-size:12px; ...

  4. MySQL,Oracle,PostgreSQL通过web方式管理维护, 提高开发及运维效率

    在开发及项目运维中,对数据库的操作大家目前都是使用客户端工具进行操作,例如MySQL的客户端工具navicat,Oracle的客户端工具 PL/SQL Developer, MSSQL的客户端工具查询 ...

  5. mongodb入门笔记

    mongodb作为nosql中排名第一的数据库,近年来使用的人数越来越多,作为开发人员,非常有必要了解下mongodb数据库.下面就给大家介绍下mongodb数据库的基本知识,有不对的地方欢迎指正,Q ...

  6. [leetcode-553-Optimal Division]

    Given a list of positive integers, the adjacent integers will perform the float division. For exampl ...

  7. TSC打印机使用教程终极版

    最近公司做一个资产采集的项目,之前做过此类项目,不过没有整理资料,借这次机会写一下,做个记录. 本教程使用的打印机型号:TSC TTP-244 Plus     官方文档 一.TSC打印机安装 1.机 ...

  8. URLConnection调用接口

    写在前面: 项目是java web,jdk1.4,weblogic 7;对方.net系统,用wcf开发的接口.对方提供接口url地址,以及说明用post方式去调用,无需传递参数,直接返回json ar ...

  9. tkinter模块常用参数(python3)

    1.使用tkinter.Tk() 生成主窗口(root=tkinter.Tk()):root.title('标题名')    修改框体的名字,也可在创建时使用className参数来命名:root.r ...

  10. Frameset框架集的应用

    Frameset框架集常用于写网站后台页面,大多数"T字型"布局后台页面,就是应用Frameset框架集来做的.Franeset框架集的优点是,他可以在同浏览器窗口显示不同页面内容 ...