一步一步學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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM