首页 > 代码库 > 一步一步学Vue(九)

一步一步学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 拦截器 ,对所有请求,加入authaxios.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!!!");});
View Code

/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="http://www.mamicode.com/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="http://www.mamicode.com/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 拦截器 ,对所有请求,加入authaxios.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‘});
View Code

/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>
View Code

/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;
View Code

/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": "老板"        }    ]}
View Code

/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:‘未授权的访问‘            });        }    }}
View Code

/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"  }}
View Code

 

一步一步学Vue(九)