一步一步学Vue(九)

时间:2023-03-10 01:33:44
一步一步学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端口,查看,运行效果如下:

一步一步学Vue(九)

这个时候,无论从浏览器地址栏还是通过跳转方式,在点击配置了 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);

运行我们的代码可以看到如下效果:

一步一步学Vue(九)

博客园对图片大小有要求,不能很好的截取,就只截取了一部分,这是登录后的效果,登录前的效果,大家可以自己测试,完整代码如下:

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