• 一步一步学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!!!");
    });
    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="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'
    });
    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
  • 相关阅读:
    原生js螺旋运动
    拉美电子游戏市场创收45亿美元
    ZOJ 3229 Shoot the Bullet
    Java的压缩、解压及压缩加密、解密解压 样例
    java环境变量配置
    git在myelispse中的安装
    java注解
    Python测试框架doctest
    python中的协程
    Flask log配置,实现按照日期自动生成日志文件
  • 原文地址:https://www.cnblogs.com/Johnzhang/p/7260888.html
Copyright © 2020-2023  润新知