一步一步学Vue(九)

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

在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"
  }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Golang语言社区

剖析Go编写的Socket服务器模块解耦及基础模块的设计

Server的解耦—通过Router+Controller实现逻辑分发 在实际的系统项目工程中中,我们在写代码的时候要尽量避免不必要的耦合,否则你以后在更新和维...

3627
来自专栏MasiMaro 的技术博文

PE文件详解二

本文转自小甲鱼的PE文件相关教程,原文传送门 咱接着往下讲解IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用! 接着我们来谈谈 ...

783
来自专栏Golang语言社区

剖析Go编写的Socket服务器模块解耦及基础模块的设计

Server的解耦—通过Router+Controller实现逻辑分发 在实际的系统项目工程中中,我们在写代码的时候要尽量避免不必要的耦合,否则你以后在更新和维...

3224
来自专栏一个会写诗的程序员的博客

功能强大的jQuery图片查看器插件

1261
来自专栏MasiMaro 的技术博文

Windows服务框架与服务的编写

从NT内核开始,服务程序已经变为一种非常重要的系统进程,一般的驻守进程和普通的程序必须在桌面登录的情况下才能运行,而许多系统的基础程序必须在用户登录桌面之前就要...

631
来自专栏mathor

配置404、Error页面以及其他常用技巧

 假设你jsp项目里java代码片出现某些问题,产生异常等等之类的,网页总是显示一堆英文提示,那你就等着电话被客户打爆吧......开玩笑的,举个简单的例子,你...

451
来自专栏鬼谷君

Django Rest Swagger生成api文档

Swagger能成为最受欢迎的REST APIs文档生成工具之一,有以下几个原因:

871
来自专栏tkokof 的技术,小趣及杂念

编程小知识之换行

之前一直依稀有些认识:不同操作系统间的换行符有所差异,但是并没有很详细的了解,对于换行符差异可能引起的问题也没有多少概念.近来遇到了一个在Android上读取文...

341
来自专栏difcareer的技术笔记

Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)[转]include <stdio.h>int func(int a, int b, int c, int d, int e,

声明:本文转自Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码),此文干货很多。

1584
来自专栏Linux驱动

15.linux-LCD层次分析(详解)

如果我们的系统要用GUI(图形界面接口),这时LCD设备驱动程序就应该编写成frambuffer接口,而不是像之前那样只编写操作底层的LCD控制器接口。 什么是...

1956

扫码关注云+社区