koa项目:云音乐应用3——注册、登录功能

前面已经实现了不同地址渲染不同页面的功能,后面我们需要实现注册和登录时的验证功能。

处理请求体

首先我们要知道,注册和登录页面中的姓名和密码都会通过post方式传递给后台。无论是node.js还是koa都无法解析request对象上的post数据,需要中间件解析,所以我们先引入koa-bodyparser

1
npm i koa-bodyparser -s

并且在router之前使用(保证router接收到的请求是解析过的):

1
2
const bodyParser = require("koa-bodyparser");
app.use(bodyParser());

处理注册

接下来我们要处理页面的注册。这里涉及几个判断:

  • 注册
    • 判断用户名是否存在
    • 判断邮箱是否存在

这些判断我们单独放置一个文件里,尽量保持主文件app.js的简洁。

  1. 先在根目录下创建一个routes文件夹,用来放置处理url的user_router.js文件(把原来app.js中的router内容挪过来
1
2
3
4
5
6
7
8
9
const Router = require("koa-router");
let userRouter = new Router();

userRouter.get('/register', ctx => {
ctx.render('register');
})
.get('/login', ctx => {
ctx.render('login')
})
  1. 紧接着我们要在userRouter中加入对post方式提交的数据的判断,我们在user_controller.js文件中定义判断方法checkUsernamedoRegister
1
2
3
4
5
6
7
8
9
10
11
12
13
// user_router.js
let userController = require("../controllers/user_controller");

userRouter.get('/user/register', ctx => {
ctx.render('register');
})
.get('/user/login', ctx => {
ctx.render('login')
})
.post('/user/check-username', userController.checkUsername)
.post('/user/do-register', userController.doRegister);

module.exports = router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// user_controller.js
const user_db = require('../modules/db');

exports.checkUsername = async(ctx) => {
let {username} = ctx.request.body;
let users = await user_db.query("SELECT * FROM user_table WHERE username = ?", [username]);
//此时users的结果就和我们在第一篇连接数据库时输出的结果一样,是一个数组,里面有符合条件的数据
if(users.length !== 0) { //已经有用户使用了申请注册的用户名
// 可以自定义返回数据,使用json结构,不同的code对应不同的状态(相当于自定义状态码)
// code: '000' msg: '可以注册‘
// code: '001' msg: '注册成功'
// code: '002' msg: '用户名已存在'
// code: '003' msg: '邮箱已存在'
// code: '004' msg: '用户名与邮箱都存在'
return ctx.body = {code: '001', msg: '用户名已存在'}
}
ctx.body = {code: '000', msg: '可以注册'}
}

我们可以现在postman中测试一下结果:

image-20180818144746684

首先测试一下使用已经注册过的username来尝试再次注册,post请求后,结果显示:

image-20180818144839080

我们再试试改一个username来测试:

image-20180818144904537

这次的结果:

image-20180818144932257

接下来我们可以接着往下来判断如何才算注册成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
exports.doRegister = async ctx => {
let {username, password, email} = ctx.quest.body;
let users = await user_db.query("SELECT * FROM user_table WHERE id is ? or email = ?", [username, email]); // 如果有重复使用的用户名或邮箱,注册都不成功
// 接下来判断用户提交的数据到底能否注册成功,可以分一下情况
// 1. 用户名已存在
// 2. 邮箱已存在
// 3. 用户名和邮箱都存在
// 4. 用户名与邮箱都不存在,注册成功
// 可以发现分析情况就是对应上面我们自定义的状态码
let user;
if(users.length !== 0) {
if(users.length > 1) return ctx.body = {code: '004', msg: "用户名与邮箱都存在"}; // 至少有一个用户使用了申请用的用户名, 同时还有一个用户使用了申请用的邮箱
user = users[0];
if(user.username === username && user.email === email) return ctx.body = {code: '004', msg: "用户名与邮箱都存在"};
if(user.username === username) return ctx.body = {code: '002', msg: "用户名已存在"};
if(user.email === email) return ctx.body = {code: '003', msg: "邮箱已存在"};
}
let result = await user_db.query("INSERT INTO user_table (username, password, email) value (?, ?, ?)", [username, password, email]);
ctx.body = {code: '001', msg: "注册成功"};
}

此时我们再用postman来测试一下我们的注册功能试试:

image-20180818150753681

结果显示是成功:

image-20180818150816809

我们再去数据库看看是否真的添加了新的数据:

image-20180818150851819

(id因为我测试的时候添加过两条数据,所以直接跳到了4)

那么我们就完成了注册的功能。

处理登录

接下来我们处理登录。登录主要是判断两件事:

  • 输入的用户名是否存在
  • 用户名与密码是否匹配

我们沿着前面的思路继续。首先在userRouter上明确接下来要进行登录操作,所以要调用判断登录是否成功的函数:

1
2
3
4
5
6
7
8
9
10
// user_router.js
userRouter.get('/user/register', ctx => {
ctx.render('register');
})
.get('/user/login', ctx => {
ctx.render('login')
})
.post('/user/check-username', userController.checkUsername)
.post('/user/do-register', userController.doRegister)
.post('/user/do-login', userController.doLogin)

然后再明确判断登录的内容:

1
2
3
4
5
6
7
8
9
10
11
12
// user_controller.js
exports.doLogin = async ctx => {
let {username, password, remember_me} = ctx.request.body;
let users = await user_db.query("SELECT * FROM user_table WHERE username = ?", [username]);
// 判断用户名是否存在
if(users.length === 0) return ctx.body = {code:'002', msg: "用户名或密码不正确"};
// 判断用户名密码是否匹配
let user = users[0];
if(user.password !== password) return ctx.body = {code: '002', msg: "用户名或密码不正确"};
// 用户名密码匹配,登陆成功
ctx.body = {code: '001', msg: "登录成功"};
}

这里住一个小的点,其实在判断中,我们第一个判断的是用户名是否存在,第二个是密码是否匹配,但是在返回给前端的信息中,我们最好不要细分,而是统一返回“用户名或密码不正确”,这样可以避免有人恶意尝试,反复提交请求。

验证登录

处理登录还有一个重要步骤,就是有些页面需要登录后才能使用,比如自己的音乐页面只有根据自己的id才会选择将自己上传的音乐显示出来,也就是说,才登录页面之后的操作需要记录我们的登录状态才可以继续进行。这里就需要用到session这个功能。

下载

我们还是先给项目下载中间件koa-session

1
npm i koa-session -S
配置

我们先给router加上检查session这个动作

1
2
3
4
5
6
7
8
9
10
11
// user_router.js
userRouter.get('/user/register', ctx => {
ctx.render('register');
})
.get('/user/login', ctx => {
ctx.render('login')
})
.post('/user/check-username', userController.checkUsername)
.post('/user/do-register', userController.doRegister)
.post('/user/do-login', userController.doLogin)
.post("user/test-session", userController.test) //检测session

然后来规定怎么检测:

第一步是我们登录的时候,要先存一个session

1
2
3
4
5
6
// user_controller.js
exports.doLogin = async ctx => {
// ... 前面的判断代码
ctx.session.user = user;
ctx.body = {code:"001", msg: "登录成功"};
}

我们可以把session对象打出来看一看,到底是一个什么样的数据:

image-20180819162956404

事实上,session就是一个对象,从数据库中选出的符合用户名和密码的这个数据是对象的key,保存的具体数据是value。

第二部是检测session

1
2
3
4
// user_controller.js
exports.test = async ctx =>{
ctx.body = ctx.session.user;
}

并且在app.js中调用中间件:

1
2
3
4
//app.js
const session = require("koa-session");
app.keys = ["hello world"];
app.use(session(app));

我们可以在postman中看到检测结果:

image-20180819165944964

这里有一个需要注意的点,在我们do-login时,浏览器有两个cookie,一个是原cookie,一个是签名过的。

image-20180819170037030

之前在cookie和session的内容中提到过,签名的作用是防止cookie被篡改,但是无法防止cookie不被别人看到,我们将原cookie使用base64解码,其实能够轻易获得用户的数据:

image-20180819170227982

这样当然存在安全隐患,所以我们应该将用户数据存在服务器端,浏览器和服务器间的通讯只携带签名过的这个session_id。

koa-session中有一个将session数据存储在服务器端的选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//app.js
let store = {
storage: {},
set: function(key, sess) {
this.storage[key] = sess;
},
get: function(key) {
return this.storage[key];
},
destroy: function(key){
delete this.storage[key];
}
}
app.use(session({store}, app));

这时我们再试试登录,就不会返回原cookie了

image-20180819171221362

此时的cookie都无法解码。但是test-session还是可以获取用户信息:

image-20180819171302683

最后,我们给用户请求加一个判断,前面我们把用户的注册、登录请求的地址都设置为/user/...,方便我们来区分,接下来读取音乐列表、上传音乐等功能我们就不用/user作为url的开头了,同时也需要确定用户是登录状态才能打开页面,因此我们在app.js文件中加上一个对url的判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//app.js
app.use(async (ctx, next) => {
let regex = /^\/user/; //用正则规定以/user开头的url
let checkUrl = regex.test(ctx.request.url);

if(checkUrl) return await next();

if(!ctx.session.user) {
return ctx.body =
`<div>
<a href = '/login'>请登录</a>
</div>`;
}
await next();
})

这时我们直接访问localhost:9000/index就会被拒绝:

image-20180819172717092