一步一步学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 条评论
登录 后参与评论

相关文章

来自专栏魏琼东

分布式消息总线,基于.NET Socket Tcp的发布-订阅框架之离线支持,附代码下载

     在前面的分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载一文之中给大家分享和介绍了一个极其简单也非常容易上的基于...

1020
来自专栏攻城狮的动态

iOS面试题梳理(三)

3527
来自专栏开发技术

spring集成mybatis实现mysql读写分离

       在网站的用户达到一定规模后,数据库因为负载压力过高而成为网站的瓶颈。幸运的是目前大部分的主流数据库都提供主从热备功能,通过配置两台数据库主从关系,...

1131
来自专栏刘望舒

Android系统启动流程(四)Launcher启动过程与系统启动流程

前言 此前的文章我们学习了init进程、Zygote进程和SyetemServer进程的启动过程,这一篇文章我们就来学习Android系统启动流程的最后一步:L...

2168
来自专栏Greenplum

Linux 常用命令(三)

curl 命令支持在线下载功能,使用方便,它支持,S,等协议,还支持PUT,POST,COOKIES,认证授权等操作。

1070
来自专栏王二麻子IT技术交流园地

《跟我学IDEA》四、配置模板(提高代码编写效率)

上一篇博文,我们学习了idea的一些实用配置,相信大家也对idea这个开发工具有了一个大概的了解。今天我们来学习模板的配置,idea提供很多模板从而提高编写代码...

7697
来自专栏一个爱瞎折腾的程序猿

asp.net core使用Swashbuckle.AspNetCore(swagger)生成接口文档

开局一张图,然后开始编,一些基本的asp.net core东西就不再赘述,本文只对Swashbuckle.AspNetCore的几个使用要点进行描述。

1181
来自专栏Albert陈凯

Hadoop数据分析平台实战——250JSSDK数据收集引擎编写离线数据分析平台实战——250JSSDK数据收集引擎编写

离线数据分析平台实战——250JSSDK数据收集引擎编写 JsSDK设计规则 在js sdk中我们需要收集launch、pageview、chargeReque...

3458
来自专栏james大数据架构

资源等待类型sys.dm_os_wait_stats

动态管理视图  sys.dm_os_wait_stats  返回执行的线程所遇到的所有等待的相关信息。可以使用该聚合视图来诊断 SQL Server 以及特定查...

2067
来自专栏魏琼东

分布式消息总线,基于.NET Socket Tcp的发布-订阅框架之离线支持,附代码下载

一、分布式消息总线以及基于Socket的实现      在前面的分享一个分布式消息总线,基于.NET Socket Tcp的发布-订阅框架,附代码下载一文之中给...

3707

扫码关注云+社区