Vue实战笔记04 - 简易网站后台的搭建之登录系统的搭建

后台登录系统的搭建

本文章是学习B站全栈之巅系列课程的学习笔记,利用笔记来吸收升华学到的知识。

学习感受是,如果有一定的nodejs+mongoDB+vue的基础,加做过一两个相关的练手项目,再学习这个课程,你会发现原来搭建一个完整有基础功能的网站,是如此的简单,会有很大的收获。
参考源码:https://github.com/morningClock/herohoner
视频学习地址:点击这里

管理员管理功能

1.在之前的基础,新增管理员账号列表页与新建管理员的页面。

2.修改请求的接口名称admin_users

3.后端新增AdminUser的数据模型。

这样就可以对管理员账号进行增删改查操作了。

加密

现在所有密码都是明文存储,在实际项目中,为了保护用户的隐私,我们在任何地方都不应该明文存储密码。我们应该要先加密后再存储到数据库,防止数据库泄漏导致用户隐私泄漏。

传统做法是使用加密算法进行加密,可以使用MD5,但是这种加密可以通过一定的手段破解。

所以为了更加安全,我们可以使用bcrypt进行密码的加密。

它的特点是:同样的密码,每一次加密的结果都不会一样。

在服务端安装bcrypt插件

npm i -D bcrypt

我们只需要存入数据库之前对密码加一次密就ok了。所以我们可以直接在数据模型中,对密码进行转换。

使用set方法,可以先对数据进行方法处理。

const mongoose = require('mongoose')const schema = new mongoose.Schema({username: { type: String },name: { type: String },password: { type: String,set(val){// set 自定义数据处理,此处用bcrypt进行密码散列加密// 每次散列加密的效果不一样// 参数1:加密字符串,参数2:加密等级(次数)return require('bcrypt').hashSync(val, 10)}}
})module.exports = mongoose.model('AdminUser', schema)

这时我们去新增管理员,就可以看到,密码明文的被打印在前端的输入框中。

如果在保存,密码又会被再次加密,这样密码就不一样了。我们希望前端看不见密码,且默认不打印出来。

这时候我们就需要用到schma的select选项属性。

它可以默认不会被查询出来,除非特指要查询出来。

const mongoose = require('mongoose')const schema = new mongoose.Schema({username: { type: String },name: { type: String },password: { type: String,// 不返回该字段查询结果select: false,set(val){// set 自定义数据处理,此处用bcrypt进行密码散列加密// 每次散列加密的效果不一样// 参数1:加密字符串,参数2:加密等级(次数)return require('bcrypt').hashSync(val, 10)}}
})module.exports = mongoose.model('AdminUser', schema)

此时,查询到的管理员详情,不会查出密码password字段。

如果非要查出密码字段,只需要设置select为true

或者在查询时增加查询语句。

AdminUser.findOne({username}).select('+password')

新增登录页面

有了管理员账号,我们就需要增加登录页面了,使用Element来简单搭建一个登录表单。



建立好静态页面后,我们就要将账号密码提交到后端,进行密码的校验工作。

其中校验使用到了,bcrypt中的校验方法compareSync进行密码校验,其中Sync是同步请求方法。

bcrypt.compareSync('需要校验的密码', '比对密码')

后端新增接口/login

/** * 登录接口* @POST  /admin/api/login* @param  {[username, password]}* @return {[]}*/app.post('/admin/api/login', async (req, res) => {const {username, password} = req.body;const AdminUser = require('../../models/AdminUser')// 取出默认不取出的password值const user = await AdminUser.findOne({username}).select('+password')// 1.根据用户查询是否存在用户if (!user) {return res.status(421).send({message:'用户不存在'})}// 2.校验密码const isValid = require('bcrypt').compareSync(password, user.password)if (!isValid) {return res.status(422).send({message:'密码错误'})}// 3. 登录成功return res.status(200).send('登录成功')})

JWT验证登录

登录校验完成,但仅仅是校验了密码,我们还需要对登录身份进行处理。我们要每次登录都了解是哪个用户访问我们的系统,那么我们就要使用到token验证技术。

每次请求头上都携带一个先前通过服务器验证通过后发放的token令牌。这样下次访问接口的时候服务器就不需要密码,直接给你提供信息了。

要怎么生成TOKEN令牌呢,我们这里使用了jsonwebtoken简称JWT标准。没有了解过的,可以参考这篇阮一峰大神的博客- JSON Web Token 入门教程。

要理解token是什么,我觉得可以把它想象成 演唱会门票,演唱会门票就是主办方发放给你的Token,它上面携带了使用时间( exp 过期时间),它上面还携带了你的信息(payload),当然它也拥有对应的校验方法以防伪造。

那么理解了它的使用,就可以直接使用jsonwebtoken了。

它的流程无非就是:

登陆成功服务端发放token–》客户端保存token–》客户端发送token校验身份–》服务端验证放行。

1.安装jsonwebtoken

npm i -D jsonwebtoken

2.登陆发放token

/** * 登录接口* @POST  /admin/api/login* @param  {[username, password]}* @return {[token]}*/app.post('/admin/api/login', async (req, res) => {const {username, password} = req.body;const AdminUser = require('../../models/AdminUser')// 取出默认不取出的password值const user = await AdminUser.findOne({username}).select('+password')// 1.根据用户查询是否存在用户if (!user) {return res.status(421).send({message:'用户不存在'})}// 2.校验密码const isValid = require('bcrypt').compareSync(password, user.password)if (!isValid) {return res.status(422).send({message:'密码错误'})}// 3. 登录成功,返回tokenconst rules = {id: user._id}// 这里的加密方法,secret字串,我们提取到一个公共的config.js中管理。const token = await jwt.sign(rules, keys.secret, { expiresIn: 60 * 60 })return res.status(200).send({token: token, 'name': user.username})})

3.客户端保存token

前端需要把接收到的token保存起来,可以保存在cookies或者localStorage中,我们这里使用localStorage

const res = await this.$http.post('/login', this.model)
// 获取登录后的token验证,Bearer为jsonwebtoken的行业标准。
localStorage.setItem("token", `Bearer ${res.data.token}`)

这样前端就可以使用token进行请求接口了。

4.客户端携带token进行请求

此处我们直接使用axios封装的请求拦截功能。

// 添加请求拦截器
http.interceptors.request.use(function (config) {// 发送请求前,携带token// console.log('请求前')config.headers.Authorization = localStorage.getItem('token')return config;
}, function (error) {// 对请求错误做些什么return Promise.reject(error);
});

当然这里只对axios请求进行了拦截处理,我们使用普通的请求提交表单是不会携带这个token的,所以会有问题。比如我们的上传图标,就使用了传统的表单提交,所以我们要简单的处理一下。



getAuthHeader () {return {Authorization: localStorage.getItem('token') || ''}
}

我们发现,如果我们要每一处上传图片都写一个同样的获取方法,那样冗余太多,也不利于维护。所以我们要提取这个方法,放到全局上。共享方法有很多方案,我们这里跟着老师使用mixin方法混入默认方法中。

在main.js中设置,将方法混入默认的方法中,所有vue示例对象都会默认拥有这个方法。

// 全局混入方法
Vue.mixin({methods:{getAuthHeader () {return {Authorization: localStorage.getItem('token') || ''}}}
})

5.服务端验证令牌

在每个请求前增加一个流程,先验证jwt,通过之后放行,

async (req, res, next) => {const token = (req.headers.authorization).split(' ').pop()// 校验tokentry {req.user = jwt.verify(token, keys.secret);// 找到用户相关信息,并返回// 继续执行接口next()}catch(err) {res.status(402).send({message:'token 无效'})}
}

但是要每个接口都添加同样的方法,这过于冗余。我们就可以考虑把它封装成为中间件。

创建midleware文件夹管理自定义中间件,创建auth.js

const jwt = require('jsonwebtoken') 
const keys = require('../config/keys')module.exports = (options) => {return async (req, res, next) => {const token = (req.headers.authorization).split(' ').pop()// 校验tokentry {req.user = jwt.verify(token, keys.secret);// 找到用户相关信息,并返回// 继续执行接口next()}catch(err) {res.status(402).send({message:'token 无效'})}}
}

接口文件中,执行中间件方法。

此处也罢resource(引入对应model),封装成中间件了瞬间简洁了。

其余全局非公开的api,都要添加这个auth进行验证

// 自定义middleware
// 校验
const authMiddleware = require('../../middleware/auth')
// 通用接口定义:根据resource请求不同接口app.use('/admin/api/rest/:resource', authMiddleware(), resourceMiddleware(), router)

前端响应时,如果发生错误,就是token不通过,则跳转回login页面,并清空原来的token

// 添加响应拦截器
http.interceptors.response.use(function (response) {// 对响应数据做点什么// console.log('请求后')return response;
}, function (error) {if(error.response.data.message){Vue.prototype.$message.error(error.response.data.message)}// 清空tokenlocalStorage.removeItem('token')router.push({path: '/login'})return Promise.reject(error);
});

前端权限路由

当然后端完成了权限接口,前端不做限制,也可以访问对应路径,只是没有数据而已。

此时我们也需要对前端进行路由的权限设置。

前端路由文件router.js中,使用meta元信息标记公开访问的页面,予以放行。

{path: '/login',component: Login,meta: { isPublic: true }
},

并添加路由守卫,进行访问权限的限制。

router.beforeEach((to, from, next) => {// to.meta.isPublic表示是否能公开访问// 没有token时,限制访问非公开页面// 记得执行完后return,不然下面的语句照样执行if (!to.meta.isPublic && !localStorage.getItem('token')) {Vue.prototype.$message.error('请先登录')return next('/login')}// 如果已经登陆,禁止访问登录页if (to.path == '/login' && localStorage.getItem('token')) {Vue.prototype.$message.success('登录成功')return next(from.path)}return next()
})

当然我们还要完善登录页面login.vue的跳转功能。

async doLogin () {const res = await this.$http.post('/login', this.model)// 获取登录后的token验证localStorage.setItem("token", `Bearer ${res.data.token}`)localStorage.setItem("name", res.data.name)// 跳转到管理系统this.$message.success('登录成功')this.$router.push('/')
}

完善

增加退出登录功能

Main.vue中的下拉列表改造

{{name}}


到此,后台管理系统的所有基本功能都已经完成了,这一路学习下来,真是学习了不少,感谢老师的教程让我看到很多对于我来说很有用的知识!!

继续努力,继续学习老师的前端页面部分实现。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部