前面的文章介绍了Vue+Vant+koa2+MongoDB实现用户注册,今天我们来讲解一下用户登陆的实现。
主要实现:
1、前端通过axios向后端发送username、password
2、后端检查用户名是否已注册、比对password是否正确。
3、后端生成JWT令牌返回给前端,前端存储于LocalStorage中。
其中,图形验证码部分将在下一节中讲解。
效果截图:
登陆成功,返回code:200 msg: 登陆成功,并打印出token
未注册用户返回:
密码错误返回:
JWT简介:
JWT全称JSON WEB TOKEN,是目前最流行的跨域认证解决方案,假设用户登陆成功之后,首先访问A页面,然后访问B页面,而这两个页面的内容都需要根据用户的身份来获取,比如说A页面为用户的订单列表,需要根据user_id来查询该用户的所有订单,B页面为用户的收货地址列表,又需要根据user_id字段来查询该用户的地址列表,而在不同页面之间,这个user_id是如何传递的呢,首先我们可以利用session,服务端通过cookie将session发送给客户端,这种方式是将session存储于服务端,还有一种方式就是服务端通过jwt将user_id、username等信息生成加密的token,客户端接收之后将其存储于LocalStroage,这样当用户访问B页面地址时,配置请求的header访问头,将token添加进请求头,服务端收到token进行解析,即可解析出user_id、username等信息,从而获取出user_id,从而B页面就会根据user_id获取到该用户的地址列表。
另外,cookie方式不支持跨域,JWT支持跨域认证,例如单点登录就是依靠JWT 实现。
一、前端部分
于.\vue-mall-mobile\mall\src 新建 Login.vue,仍然采用Vant的输入框控件:
<template> <div id="login"> <van-cell-group> <van-field v-model="username" label="账号" /> <van-field v-model="password" type="password" label="密码" /> </van-cell-group> <van-button square block type="info" native-type="submit" size="normal" @click="Login"> 登陆 </van-button> </div> </template> <script> import { Field } from ‘vant‘; import { Button } from ‘vant‘; import { Toast } from ‘vant‘; import { Cell, CellGroup } from ‘vant‘; import ajax from ‘@/api‘;
export default { components:{ [Field.name]: Field, [Button.name]: Button, [Toast.name]: Toast, [Cell.name]: Cell, [CellGroup.name]: CellGroup, }, data() { return { username:‘‘, password:‘‘, } } } </script>
mothods中添加Login()方法:
async Login() { let { username, password} = this.$data; let res = await ajax.login(username, password); console.log(res) }
在src\api\index.js中添加,通过axios将username、password发送到后端接口:
login(username = ‘‘, password = ‘‘) { return axios.post(‘localhost:3000/users/loginUser‘,{username, password}) }
二、后端部分:
在后端routes\users.js中添加响应路由:
/** * 用户登陆 */ router.post(‘/loginUser‘, async function (ctx) { let {username, password} = ctx.request.body; if(!username || !password) return ctx.body = {code: 4020,msg: ‘请填写完整的注册信息‘}; let args = {username, password}; const userData = await userService.accountLogin(args); ctx.body = (userData.code === 200) ? {code: 200, msg: ‘登陆成功‘, token: jwt._createToken(userData)} : userData })
在service\userService中添加处理登陆方法 accountLgoin({username, password}):
async accountLogin({username, password}) { const userDoc = await UserModel.findOne({username}); if(!userDoc) return {code: 0, msg: ‘该用户尚未注册‘}; let result = await userDoc.comparePassword(password, userDoc.password); // 进行密码比对是否一致 return !result ? { code: -2, msg: ‘密码不正确‘ } : { code: 200, _id: userDoc._id, userName: userDoc.userName, gender: userDoc.gender, avatar: userDoc.avatar, mobilePhone: userDoc.mobilePhone, email: userDoc.email, year: userDoc.year, month: userDoc.month, day: userDoc.day }; }
这里的userDoc.comparePassword()方法,是我们在定义UserModel是添加的方法,bcryptjs的bcrypt.compare()方法来实现对比我们在输入框中输入的秘密和保存在数据库里的hash处理后的密码。
生成JWT令牌,\utils\jwt.js中的_createToken()方法:
const jwt = require(‘jsonwebtoken‘); /** * 创建 Token */ const _createToken = (userInfo) => { // JWT 格式 token | 有效时间 1 小时 return jwt.sign({ userInfo }, secret, { expiresIn: ‘1h‘ }); };
这里的 {userInfo} 存储了用户的id、username、性别等信息,以token令牌的方式存储于客户端,也就是前端,等到客户端发送请求时,在header请求头中,加入该token,后端通过jwt.virefy()将token中存储的信息解析出来,解析方法如下,从而使后端代码可以通过解析出来的信息认定用户的身份。
const _verify = (token) => { return jwt.verify(token, secret, (error, decoded) => { console.log(decoded); }); };
三、前端储存token
方法一:可以直接在axios返回时,用LocalStorage.setItem()将token写入,
LocalStorage.setItem(‘user-token‘,res.token);
这样,我们下次想要访问该token时,直接:
let token = LocalStorage.getItem(‘user-token‘)
不过我们应该熟悉Vuex状态管理,为后续开发中大型应用做准备,方法二:
在axios返回时,调用this.$store.commit()
(res.token)&&(this.$store.commit(‘setUserToken‘, res.token));
setUserToken位于.\src\store\index.js:
export default new Vuex.Store({ state: { token: ‘‘, }, mutations: { setUserToken(state, token) { alert(token); state.token = token; } }, }
到这里,完整的登陆功能就实现啦。下一节我们会实现图片验证码。
如果文章中有错误之处,欢迎大家交流指正。