Koa+JSON-WEB-TOKEN+bcrypt实现登录注册

前端我用的是vue+axios,项目地址nothing-left

项目结构

在用vue-cli构建项目脚手架的基础上新建一个app.js和server文件夹

1
2
3
4
5
6
7
8
9
10
11
12
├─ app.js//koa入口文件
└─ server//后端
├─ config //数据库配置
│ └─ db.js
├─ controllers //对models里面的方法进行选择性的组装,来响应前端不同的请求
│ └─ users.js
├─ models //为controllers提供一些方法
│ └─ users.js
├─ routes //定义路由,把前端不同的请求分配给controllers相对应的方法
│ └─ auth.js
└─ schema //数据库表结构
└─ users.js

项目依赖

koa-router:对路由进行控制

koa-logger:日志模块,当前后端进行交互时会在控制台打印相关信息

koa-bodyparser:当前端发送post请求时,把请求里面的数据解析到ctx.request.body中

sequelize:用于后端操纵数据库

koa-jwt:用于实现基于JSON-WEB-TOKEN的登录验证

bcryptjs:用bcrypt的方式对密码进行加密和解密

app.js

app.js作为koa的启动文件。在app.js中,我们需要:

1.引入koa

1
const Koa = require('koa');

2.创建一个koa对象

1
const app = new Koa();

3.引入koa中间件

1
2
3
4
const koaRouter = require('koa-router');
const logger = require('koa-logger');
const jwt = require('koa-jwt');
const bodyparser = require('koa-bodyparser');

4.在koa对象中注册各中间件

1
2
3
4
app.use(bodyparser());
app.use(logger());
const router = koaRouter();
app.use(router.routes());

5.设置监听端口

1
2
3
app.listen(3001, () => {
console.log('Koa is listening in 3001');
});

6.将app暴露出去

1
module.exports =  app;

搭建数据库

1.我采用可视化工具HediSQL来操作MySQL。在HediSQL上,我们先创建一个名为nothing-left的数据库,并创建一张用于存储用户信息的users表:

名称 数据类型 长度 注释
id INT 10 用户的id
username VARCHAR 50 用户名
password VARCHAR 128 密码

2.接着,我们将users表导出到server文件夹下,形成users.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = function(sequelize, DataTypes) {
return sequelize.define('users', {
username: {
type: DataTypes.STRING(50),
allowNull: false,
},
password: {
type: DataTypes.STRING(128),
allowNull: false
},
id: {
type: DataTypes.INTEGER(10),
primaryKey: true,
allowNull: false,
autoIncrement: true
}
}, {
tableName: 'users'
});
};

3.在config下新建db.js用于初始化Sequelize和数据库的连接。

1
2
3
4
5
6
7
8
9
10
11
12
const Sequelize = require('sequelize');

//xxx为数据库密码
const NothingLeft = new Sequelize(`mysql://root:xxx@localhost/nothing-left`,{
define:{
timestamps:false
}
});

module.exports = {
NothingLeft
}

models

在models文件夹下我们新建users.js用于定义一些对数据库进行各种操作的方法。在users.js文件里,我们需要定义两个方法,一个是通过用户名查找到某个用户的信息,另一个是创建新的用户。

1.首先,我们需要在users.js里引入数据库和users表,并用sequelize去实例化一个Users对象:

1
2
3
4
const db = require('../config/db.js');
const usersModel = '../schema/users.js';
const NothingLeftDB = db.NothingLeft;
const Users = NothingLeftDB.import(usersModel);

2.接着,我们来定义一个通过用户名查找用户信息的方法:

1
2
3
4
5
6
7
8
9
const getUserByName = async function (name) {
const userInfo = await Users.findOne({
where:{
username:name
}
})

return userInfo;
};

async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

对于其中牵涉到的promiseGenerator方面的知识,暂时理解还不够。

3.定义一个用于创建新用户的方法

1
2
3
4
5
6
7
8
9
10
const bcrypt = require('bcryptjs')
const createUser = async function (data) {
const password = bcrypt.hashSync(data.password, bcrypt.genSaltSync(10))
await Users.create({
id: 1,
username: data.userName,
password: password
})
return 200//注册成功
};

在这里,我们需要引入bcryptjs为我们的密码在存入数据库前进行加密

4.将方法们暴露出去供controllers调用

1
2
3
4
module.exports = {
createUser,
getUserByName
};

controllers

1.引入相关依赖和models/users.js

1
2
3
const users = require('../models/users.js');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

2.实现登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const postUserAuth = async function (ctx) {
const data = ctx.request.body; // post过来的数据存在request.body里
const userInfo = await users.getUserByName(data.name)
if (userInfo != null) { // 如果查无此用户会返回null
if (!bcrypt.compareSync(data.password, userInfo.password)) {
ctx.body = {
code: 202,
info: '密码错误!'
}
} else {
const userToken = {
name: userInfo.username,
id: userInfo.id
}
const secret = 'nothing-left' // 指定密钥
const token = jwt.sign(userToken, secret) // 签发token
ctx.body = {
code: 200,
token: token // 返回token
}
}
} else {
ctx.body = {
code: 201,
info: '用户不存在!' // 如果用户不存在返回用户不存在
}
}
};

当postUserAuth接收到前端post过来的数据时,会调用models里面的方法查看该用户名是否存在。如果不存在,则返回相关信息。若存在,postUserAuth会将前端post过来的密码和数据库中该用户名对应的密码进行比较,若密码一致,就签发token,否则返回相关信息。

3.实现注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const register = async function (ctx) {
const data = ctx.request.body
const sameUserName = await users.getUserByName(data.userName)
if(sameUserName == null) {
await users.createUser(data)
ctx.body = {
code: 200//注册成功
}
} else {
ctx.body = {
code: 201//该用户名已被注册
}
}
};

4.将方法导出

1
2
3
4
module.exports = {
register,
postUserAuth
}

定义路由

在routes文件夹下新建auth.js

1
2
3
4
5
6
7
8
9
10
const auth = require('../controllers/users.js');
const koaRouter = require('koa-router');
const router = koaRouter();

router.post('/users', auth.postUserAuth)
router.post('/users/register', auth.register)

module.exports = {
router
};

跳转拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/main.js
router.beforeEach((to, from, next) => {
const token = sessionStorage.getItem('demo-token')
if (to.path === '/') { // 如果是跳转到登录页的
if (token !== 'null' && token !== null) {
next('/home') // 如果有token就转向home不返回登录页
}
next() // 否则跳转回登录页
} else {
if (token !== 'null' && token !== null || to.path === '/register') {
Vue.prototype.$http.defaults.headers.common['Authorization'] = 'Bearer ' + token // 注意Bearer后有个空格
next() // 如果有token就正常转向
} else {
next('/')
}
}
});

这个时候,从后端给我们传回来的token就派上大用处。有token就说明我们的身份是经过验证的,否则就是非法的。
vue-router提供了页面跳转的钩子,我们可以在router跳转前进行验证,如果token存在就跳转,如果不存在就返回登录页。

解析token

1
2
//app.js
router.use('/api', jwt({secret: 'nothing-left'}), api.router.routes());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//  components/login.vue

login: function() {
if (!this.checkMessage()) {
return
}
let obj = {
name: this.cellphone,
password: this.password
}
const result = this.$http.post('/auth/users', obj);
result.then((res) => {
switch(res.data.code) {

case 200:
this.isLoading=true;
sessionStorage.setItem('demo-token', res.data.token)
this.$router.push('/home');
break;

case 201:
this.showSnackbar("用户名不存在!");
sessionStorage.setItem('demo-token', null)
break;

case 202:
this.showSnackbar("密码不正确!");
sessionStorage.setItem('demo-token', null)
break;

}
}, (err) => {
console.log(err)
sessionStorage.setItem('demo-token', null) // 将token清空
})
return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//  components/register.vue
regist: function() {
if (!this.checkMessage()) {
return
}
let obj = {
userName: this.cellphone,
email: this.code,
password: this.firstPassword
}
const result = this.$http.post('/auth/users/register', obj)
result.then((res) => {
switch(res.data.code) {
case 200:
this.showSnackbar("注册成功")
this.$router.push('/')
break

case 201:
this.showSnackbar("该用户名已被注册")
break
}
}, (err) => {
console.log(err)
})
}

参考项目/文章