如何用Eggjs从零开始开发一个项目(2)
在上一篇文章,我们已经使用Sequelize连接上了数据库,并能进行简单的数据库操作,在此基础上,我们试着来开发一个完整的项目。这篇文章我们从用户的注册、登录着手,试着开发用户模块的相关的代码。
用户注册
1. 注册逻辑
用户注册的逻辑很简单:
- 客户端:用户输入输入账号,密码等信息进行用户注册;
- 服务端:接收到客户端提交的注册信息后,进行字段的检验(是否必填、字段长度等),字段符合要求后,根据用户注册的账号查询数据库,根据返回结果判断该用户是否是新用户,如果是新用户,将用户信息写入到数据库,完成注册流程。
2. 用户密码处理
客户端用户提交数据后,服务端验证通过进行数据库写入,但是其中用户密码是敏感信息,为了服务安全考虑,不能直接将明文密码写入到数据库,防止数据库被攻击,用户密码泄露。所以一般在存储用户密码时,会先对用户密码进行加盐hash处理,这样哪怕数据库存储的密码泄露,其他人也无法通过处理后的密码进行登录。这里使用哈希算法对密码进行处理,因为哈希的特性是不可逆,具体的细节可以参考为什么说 MD5 是不可逆的?。
这里我们对密码的处理方法叫哈希,即hash,它不是一种加密算法,而是一种摘要算法。网上很多文章对这两个概念都混淆了,其实加密跟哈希是完全不同的两个概念。具体可参考:哈希(Hash)与加密(Encrypt)的基本原理、区别及工程应用。
哈希可以被暴力破解,加盐可以很大程度上增加破解难度。所以对密码的处理方式一般是每个用户密码对应一个随机盐,跟密码存储在一起,这样就算是脱库了,别人想破解也很难,因为要针对每个密码去破解。所以最后我们生成hash的公式为:
R = H(password + randomSalt)
如果你还觉得不安全,你甚至还可以在代码里再写一个固定盐值,用两个盐进行hash,或者多次hash等等...
废话不多说,我们下面来写代码。
首先,我们安装一下bcryptjs
,我们使用它对密码进行加盐哈希和比对:
npm install bcryptjs --save
然后我们把这两个方法写到app/extend/helper.js
:
const bcrypt = require('bcryptjs');
module.exports = {
encrypt(password) {
const salt = bcrypt.genSaltSync(10); //加盐
const hash = bcrypt.hashSync(password, salt); //哈希(同步调用)
return hash;
},
compare(password, hash) {
return bcrypt.compareSync(password, hash); //比对
}
};
这里你可能会有点疑问:为什么bcrypt.compareSync
方法没有salt的入参呢?这里说明一下,bcrypt.hashSync
在生成hash时已经将盐值存储在结果中了,所以他算出来的结果是hash串和salt的结合,这样我们就不用往数据库存储盐值了,比较方便。
这样我们在项目里就可以通过this.ctx.helop.encrypt
和this.ctx.helop.compare
的方式去使用这些公用的方法了。
然后,在UserController
中添加一个register
方法:
async register() {
const params = this.ctx.request.body;
// 参数校验
if (!params.name || !params.password || !params.phone || !params.email) {
this.ctx.body = {
code: '500',
msg: '参数不合法'
};
}
// 查询该用户是否已经注册
const user = await this.ctx.model.User.findOne({ where: {
name: params.name
} });
if (user) {
this.ctx.body = {
code: '500',
msg: '该用户已存在'
};
}
// 插入数据库
const result = await this.ctx.model.User.create({
...params,
password: this.ctx.helper.encrypt(params.password)
});
if (result) {
this.ctx.body = {
code: '200',
msg: '注册成功'
};
}
}
最后,添加路由:
// app/router.js
router.post('/register', controller.user.register);
3.功能测试
测试一下,用postman创建一个post请求到localhost:7001/register
,传入注册用户信息:
{
"name":"xiaoming",
"password":"test1234",
"phone":"13412341234",
"email":"test@gmai.com"
}
服务端返回“注册成功”的提示语,这时候我们去数据库就能看到这条数据了,而且密码是一坨看不懂的密文,这个时候我们用同样的数据再次发起请求,服务端返回“该用户已注册”,说明我们的注册功能已经完成了!
用户登录
用户登录的逻辑很简单,就是一个客户端传入的用户名密码与服务器存储的相比较,匹配就登录成功,不然就是用户名密码错误。但是,我们还需要额外考虑一个问题,http请求是无状态的,那我们怎么去记住用户的登录状态和登录信息呢,并且每次向服务端发起请求都带上这些信息呢?
常用的用户认证方式有两种,一种是通过Cookie实现,一种是Token的实现方式。关于用户授权认证可以看看这篇文章:授权认证登录之 Cookie、Session、Token、JWT 详解,Eggjs官网也有一个章节讲了Cookie和Session相关的知识Cookie 与 Session,学习服务端,掌握这些知识还是很必要的。
在这里,我们选择JWT作为我们的解决方案。关于JWT,这里提供两篇文章作为参考,JSON Web Token 入门教程,Introduction to JSON Web Tokens。OK,原理看完以后我们来写代码。
首先我们安装egg-jwt
插件:
npm install egg-jwt --save
引入插件:
// app/config/plugin.js
jwt: {
enable: true,
package: "egg-jwt"
}
配置jwt secret:
// app/config/config.default.js
config.jwt = {
secret: '12312456
};
OK,我们下面编写代码的代码:
// app/controller/user.js
async login() {
const params = this.ctx.request.body;
if (!params.name || !params.password) {
this.ctx.body = {
code: '500',
msg: '参数不合法'
}
}
const user = await this.ctx.model.User.findOne({ where: {
name: params.name
} });
if (!user) {
this.ctx.body= {
code: '500',
msg: '用户名密码错误'
}
}
//校验密码
const checkPassword = this.ctx.helper.compare(
params.password,
user.password
);
if (checkPassword) {
// 根据用户名称创建token,过期时间为2小时
const token = this.app.jwt.sign({
name: user.name
}, this.app.config.jwt.secret, { expiresIn: '2h' });
this.ctx.body = {
code: '200',
data: token
}
} else {
this.ctx.body = {
code: '500',
msg: '用户名密码错误'
}
}
}
配置路由:
// app/router.js
router.post('/login', controller.user.login);
代码完毕,让我们测试一下,创建一个post请求到localhost:7001/login
,输入用户名密码:
{
"name":"xiaoming",
"password":"test1234"
}
结果如下:
{
"code": "200",
"data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieGlhb21pbmciLCJpYXQiOjE2MDg1NTkzMTYsImV4cCI6MTYwODU2NjUxNn0.SyXAhVvrwAql-4FzaZrlEs6dsEJ4wXbdjQsHv43CSOI"
}
成功了,这一大坨就是我们创建的token。
嗯……我们是拿到了token,但是怎么用呢?还记得我们最开始写的两个接口吗?我们现在给他配置jwt验证,然后试试不登录能不能访问。
// app/router.js
router.post('/createUser', app.jwt, controller.user.createUser);
router.get('/getUsers', app.jwt, controller.user.getUsers);
然后我们重新测试一下这两个接口,服务端返回401 Unauthorized
,这说明验证生效了。那我们在请求的时候把刚才登录接口获取到的token设置在Authorization
的Bearer Token
字段中,再试一次,成功了!
如果你跟我走到了这一步,那说明你已经距离一个合格的服务端开发者更近了一步,但是这里我们好像并没有用到token
里面携带的信息,下一篇我们将会解析token
携带的信息,知道每次都是哪个用户在访问我们的服务,而且我们将优化我们的代码,让它看起来更简洁、合理。
如何用Eggjs从零开始开发一个项目(2)的更多相关文章
- 如何用Eggjs从零开始开发一个项目(1)
前言 "纸上得来终觉浅,绝知此事要躬行."虽然node一直在断断续续地学,但总是东一榔头西一榔头的,没有一点系统,所以打算写一个项目来串联一下之前的学习成果. 为什么选择Eggjs ...
- 如何用Eggjs从零开始开发一个项目(3)
上一篇中我们编写了用户注册登录.登录的代码,学习了如何进行用户的认证(JWT),如何安全地存储用的密码(hash).这一篇我们有以下2个任务: 获取token中的数据: 通过model来同步数据库. ...
- Cordova之如何用命令行创建一个项目(完整示例)
原文:Cordova之如何用命令行创建一个项目(完整示例) 1. 创建cordova项目 (注意:当第一次创建或编译项目的时候,可能系统会自动下载一些东西,需要一些时间.) 在某个目录下创建cordo ...
- YII框架开发一个项目的通用目录结构
YII框架开发一个项目的通用目录结构: 3 testdrive/ 4 index.php Web 应用入口脚本文件 5 assets/ 包含公开的资源文件 6 css/ 包含 CSS 文件 7 ima ...
- github如何多人开发一个项目
github如何多人开发一个项目 一.总结 一句话总结:a.点项目里面的Settings->Collaborators,来添加参与者(比如github用户名), b.向他发送项目的link,让他 ...
- 从零开始, 开发一个 Web Office 套件 (2): 富文本编辑器
书接前文: 从零开始, 开发一个 Web Office 套件 (1): 富文本编辑器 这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Of ...
- 从零开始, 开发一个 Web Office 套件 (3): 鼠标事件
这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Office 套件, 包括: 文档, 表格, 幻灯片... 等等. 对应的Github r ...
- 从零开始, 开发一个 Web Office 套件(4):新的问题—— z-index
<从零开始, 开发一个 Web Office 套件>系列博客目录 这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Office ...
- 《从零开始, 开发一个 Web Office 套件》系列博客目录
这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Office 套件, 包括: 文档, 表格, 幻灯片... 等等. 对应的Github r ...
随机推荐
- HDU2065 "红色病毒"问题 【组合数学 二项式定理】
HDU2065 "红色病毒"问题 Description: 医学界发现的新病毒因其蔓延速度和Internet上传播的"红色病毒"不相上下,被称为"红色 ...
- hdu 6794 Tokitsukaze and Multiple 前缀和思想+思维
题意: t组输入,给你一个长度为n的数组,你每次可以从数组中找到a[i]和a[i+1],然后用a[i]+a[i+1]这个新元素来覆盖掉a[i]和a[i+1]的位置(1<=i<n),从而数组 ...
- 【2020杭电多校】Total Eclipse 并查集+思维
题目链接:Total Eclipse 题意: t组输入,给你一个由n个点,m条边构成的图,每一个点的权值是ai.你每一次可以选择一批联通的点,然后让他们的权值都减去1.问最后把所有点的权值都变成0需要 ...
- Codeforces Round #653 (Div. 3) D. Zero Remainder Array (数学,模拟)
题意:有一组数,刚开始时\(x=0\),每次可以让\(x\)++或让某一个元素+=\(x\)后\(x\)++,每个元素只能加一次\(x\),问最少操作多少次使得所有元素能被\(k\)整除. 题解:每个 ...
- SQL Server 新安装启用sa用户/sa用户登录提示管道另一端无进程
安装时只用windows验证 安装完成后: 首先选中服务器(右键)->属性->安全性->服务器身份验证修改为"SQL SERVER和WINDOWS身份验证模式"其 ...
- WPF 之命令(七)
一.前言 事件的作用是发布和传播一些消息,消息送达接收者,事件的使命也就完成了,至于消息响应者如何处理发送来的消息并不做规定,每个接收者可以使用自己的行为来响应事件.即事件不具有约束力. 命令 ...
- 【转】Kubernetes scheduler学习笔记
简介 Kubernetes是一个强大的编排工具,可以用来很方便的管理许多台机器,为了使机器的资源利用率提高,同时也尽可能的把压力分摊到各个机器上,这个职责就是由scheduler来完成的. Kuber ...
- TestNG学习-依赖
背景: 有时,需要按特定顺序调用测试方法.例如: 在运行更多测试方法之前,请确保一定数量的测试方法已经完成并成功. 在希望将初始化的方法也用作测试方法的同时初始化测试项目. TestNG允许使用注解或 ...
- HTML教程(看完这篇就够了)
HTML教程 超文本标记语言(英语:HyperText Markup Language,简称:HTML)是一种用于创建网页的标准标记语言.您可以使用 HTML 来建立自己的 WEB 站点,HTML 运 ...
- Canvas & encryption image src
Canvas & encode image src 使用 Canvas 加密图片,防盗链 前端黑科技 https://telegram.org/ binary encode ??? https ...