当前位置:网站首页>Node.js: express + MySQL实现注册登录,身份认证
Node.js: express + MySQL实现注册登录,身份认证
2022-07-06 12:11:00 【掉头发类型的选手】
这篇文章中有些内容像字段校验,项目结构等部分在另一篇文章中有写到:Node.js: express + MySQL的使用_掉头发类型的选手的博客-CSDN博客
一,实现注册
1,注册需要将新用户的账号和密码写入数据库,账号可以直接写入数据库,但密码一般不会直接存入到数据库中,会将密码加密后存入数据库中,能够提高账号的安全性。
2,在登录的时候再将密码通过同样的方式进行加密,与数据库中的存储的密码进行比对,相同的话则登录成功。
3,实现
(1),先在数据库中创建一个表用来存储信息,我创建的这个表名是 user_login
(2),密码加密用到一个包 bcryptjs ,这个包可以对密码进行加密,用npm将它安装到项目中。
(3),目录结构,在路由模块,处理函数模块,字段校验模块下都新建一个文件,用来完成这部分内容。
app.js
// 引入express
const express = require("express");
// 创建服务器的实例对象
const app = express();
// 引入校验规则的包,在定义错误级别的中间件时会用到
const joi = require("joi");
// 配置解析表单数据的中间件 内置中间件,只能解析application/x-www-form-urlencoded格式的数据
app.use(express.urlencoded({ extended: false }));
// 封装res.send() ,必须在路由之前封装
/**
* 不管是输出正确的数据还是错误的信息,每次都需要写这么多东西
* res.send({ state: 0, message: "查询成功", data: result })
* 封装之后不需要写这么多
*/
app.use((req, res, next) => {
// 定义一个输出的函数
res.output = function (err, status = 1, data) {
res.send({
status,
message: err instanceof Error ? err.message : err,
data,
});
};
next();
});
// 导入并使用登录,注册的路由模块
const loginRouter = require("./router/login");
app.use(loginRouter);
// 导入并使用路由模块
const inforRouter = require("./router/userInfor");
app.use(inforRouter);
// 在所有路由下面调用错误级别的中间件
app.use((err, req, res, next) => {
// 验证失败导致的错误
if (err instanceof joi.ValidationError) return res.output(err);
// // 身份认证失败后的错误
// if(err.name === 'UnauthorizedError') return res.cc('身份认证失败!')
// 未知的错误
res.output(err);
next();
});
// 启动服务器
app.listen(3007, () => {
console.log("Server running at http://127.0.0.1:3007");
});
因为app.js是整个项目的入口文件,所有所有的路由都应该在这个文件中导入并使用。
router文件夹下的login.js文件
const express = require("express");
const router = express.Router();
// 导入校验规则的中间价
const expressJoi = require("@escook/express-joi");
// 引入规则
const { login_rules } = require("../schema/login");
// 引入处理函数
const login_handler = require("../router_handler/login");
// 注册
router.post("/register", expressJoi(login_rules), login_handler.userRegister);
// 将路由导出
module.exports = router;
和上一篇内容一样,这个文件中注册路由,并从router_handler中导入处理函数,导入校验规则的中间件和规则,最后将路由导出,在app.js文件中使用。
router_handler文件夹下的login.js文件 (处理函数)
// 导入数据库模块
const db = require("../db/index");
// 引入对密码进行加密的包
const bcryptjs = require("bcryptjs");
// 注册处理函数并导出
exports.userRegister = (req, res) => {
// 先取到从客户端传来的值
let { username, password } = req.body;
/**
* 注册的时候用户名是唯一的,不能与其他人的用户名一样
* 在将信息写入数据库之前,要先检查用户名是否被占用
*/
// 查询有无相同的用户名
const sql = "select username from user_login where username=?";
// 执行查找sql语句
db.query(sql, username, (err, results) => {
// sql语句执行失败
if (err) return res.output(err);
// 执行成功,如果有数据,则说明有相同的用户名
if (results.length === 1) return res.output("用户名被占用");
// 执行成功,用户名没被占用
console.log('加密之前', password);
// 对密码进行加密,第二个参数可以提高密码的安全性,几也行
password = bcryptjs.hashSync(password, 10);
console.log('加密过后', password);
// 定义新语句,增加用户
const sqlStr = "insert into user_login set ?";
// 执行增加sql语句
db.query(sqlStr, { username, password }, (err, results) => {
// 执行sql语句失败
if (err) return res.output(err);
// 执行sql语句成功 但影响行数不为1
if (results.affectedRows !== 1) return res.output("用户注册失败!");
// 执行成功,影响行数为1
res.output("注册成功!");
});
});
};
(1),先获取到从客户端传来要注册的用户名和密码,解构用户名和密码的时候要用let,因为在下面的操作中需要给密码加密,修改了变量。用const,重新定义一个值也行。
(2),在注册的过程中分为三步,第一步先将用户名在数据库中查询有没有相同的值,用户名不能相同,存在相同的话返回用户名被占用。
(3),如果没有相同的用户名和密码,则可以使用,将用户设定的密码进行加密,使用bcryptjs包进行加密。使用hashSync()方法,第一个参数是原密码,第二个参数可以提高密码的安全性,是一个数字。
(4),加密过后可以在终端输出查看加密后的密码,可以将数据存储到数据库中,使用insert语句,若数据库语句执行成功,但影响行数不为1,也属于失败。
schema文件夹下的login.js文件(校验规则,在router目录下的login.js文件中使用)
// 导入校验规则的包
const joi = require("joi");
// 确定规则
const username = joi.string().alphanum().min(1).max(16).required();
const password = joi.string().pattern(/^[\S]{6,12}$/).required();
// 导出规则
exports.login_rules = {
body: {
username,
password,
},
};
都写好后用postman测试一下,先测试校验规则能不能用,输一个错的,报错密码不符合正则。
然后把密码换成符合校验的:
注册成功,在终端看一下加密之前和之后的密码,数据库中也有了一条新数据。
之后再点击一次send,这次是进行注册的时候数据库中已有数据,并且用户名被占用。
报错,用户名被占用。
二,实现登录
实现登录,会用到一个包 jsonwebtoken ,这个包能够生成token。
文件结构:
这次多了一个全局的配置文件,config.js,这个文件设置生成token时的加密方式的密钥,和token的持续时间,密钥和时间直接写在文件中也可以,但加密需要用到,解密也需要用到,为了方便将他抽离出来。密钥可以随便设置。
config.js文件
// 全局的配置文件
module.exports = {
/**
* 设置token加密和解密用到的密钥
*/
jwtSecretKey: 'c^_^h',
/**
* 设置token的有效期
*/
expiresIn: '10h',
}
router文件夹下的login.js文件(在这个文件中将路由配置好)
const express = require("express");
const router = express.Router();
// 导入校验规则的中间价
const expressJoi = require("@escook/express-joi");
// 引入规则
const { login_rules } = require("../schema/login");
// 引入处理函数
const login_handler = require("../router_handler/login");
// 注册
router.post("/register", expressJoi(login_rules), login_handler.userRegister);
// 登录,登录的时候字段也是相同的规则
router.post("/login", expressJoi(login_rules), login_handler.userLogin);
// 将路由导出
module.exports = router;
注册和登录都用到了用户名和密码,这两个的规则是相同的。
router_handler文件夹下的login.js文件(登录的处理函数)
// 导入数据库模块
const db = require("../db/index");
// 引入对密码进行加密的包
const bcryptjs = require("bcryptjs");
// 导入生成token的包
const jwt = require("jsonwebtoken");
// 导入全局的配置文件,密文
const config = require("../config");
// 注册处理函数
exports.userRegister = (req, res) => {
// 先取到从客户端传来的值
let { username, password } = req.body;
/**
* 注册的时候用户名是唯一的,不能与其他人的用户名一样
* 在将信息写入数据库之前,要先检查用户名是否被占用
*/
// 查询有无相同的用户名
const sql = "select username from user_login where username=?";
// 执行查找sql语句
db.query(sql, username, (err, results) => {
// sql语句执行失败
if (err) return res.output(err);
// 执行成功,如果有数据,则说明有相同的用户名
if (results.length === 1) return res.output("用户名被占用");
// 执行成功,用户名没被占用
// 定义新语句,增加用户
const sqlStr = "insert into user_login set ?";
console.log("加密之前", password);
// 对密码进行加密,第二个参数可以提高密码的安全性,几也行
password = bcryptjs.hashSync(password, 10);
console.log("加密过后", password);
// 执行增加sql语句
db.query(sqlStr, { username, password }, (err, results) => {
// 执行sql语句失败
if (err) return res.output(err);
// 执行sql语句成功 但影响行数不为1
if (results.affectedRows !== 1) return res.output("用户注册失败!");
// 执行成功,影响行数为1
res.output("注册成功!");
});
});
};
// 登录处理函数
exports.userLogin = (req, res) => {
// 接收表单数据
const { username, password } = req.body;
// 先查找用户名是否在数据库中,定义sql语句
const sql = "select * from user_login where username=?";
// 执行语句
db.query(sql, username, (err, results) => {
if (err) return res.output(err);
// 语句执行成功,但没有相应的username
if (results.length !== 1) return res.output("登录失败");
// 语句执行成功,也有相应的username
// 进行密码的比较
// 前面是客户端的密码,后面是数据库中存储经过加密的密码
const compareResult = bcryptjs.compareSync(password, results[0].password);
// 会返回true或false
if (!compareResult) {
return res.output("密码错误,登录失败!");
}
// 密码比对正确,在服务端生成token字段
// 获取到用户的信息,剔除掉密码,生成token
const user = { ...results[0], password: "" };
// 对用户的信息进行加密,生成token字符串,参数2和参数3可以直接写,也可以抽出去
const tokenStr = jwt.sign(user, config.jwtSecretKey, {
expiresIn: config.expiresIn,
});
// 调用res.send将token响应给客户端
res.output("登录成功", 0, "Bearer " + tokenStr);
});
};
(1),先接收客户端传来的用户名和密码,然后在数据库中寻找是否有对应的用户名,如果没有,就是用户名不对,登录失败。
(2),如果有对应的用户名,要比对密码是否相同,比对密码还是用到 bcryptjs 包,用到其中compareSync()方法,这个方法有两个参数,第一个是从客户端传来的密码,第二个参数是在数据库中存储经过加密的密码,根据结果会返回true或false,如果不相同,登录失败。
(3),如果密码相同登录成功,要生成token返回客户端,用到 jsonwebtoken 包,sign()方法,它根据用户的信息生成token,用户信息一般是会将密码去掉的。第二个参数是密钥,第三个参数是时间,有多长时间的有效期,最后将token返回到客户端。
(4),token客户端不能直接用,需要在前面加 'Bearer ',返回的时候将这个加上,前端就能直接用了。
登录成功。
三,登录内与登录外
项目的功能分为登录内和登录外的功能,比如一个博客系统,在没有登录时,你可以查看里面的文章,但只有登录之后,才能发表文章。
登录内的接口要在请求时在请求头里上送token,进行身份认证。只在app.js里有修改,其他文件不变。
app.js
// 引入express
const express = require("express");
// 创建服务器的实例对象
const app = express();
// 引入校验规则的包,在定义错误级别的中间件时会用到
const joi = require("joi");
// 配置解析表单数据的中间件 内置中间件,只能解析application/x-www-form-urlencoded格式的数据
app.use(express.urlencoded({ extended: false }));
// 封装res.send() ,必须在路由之前封装
/**
* 不管是输出正确的数据还是错误的信息,每次都需要写这么多东西
* res.send({ state: 0, message: "查询成功", data: result })
* 封装之后不需要写这么多
*/
app.use((req, res, next) => {
// 定义一个输出的函数
res.output = function (err, status = 1, data) {
res.send({
status,
message: err instanceof Error ? err.message : err,
data,
});
};
next();
});
// 在路由之前配置解析token的中间件
const { expressjwt: jwt } = require("express-jwt");
// 解析token需要token的密钥
const config = require("./config");
// 定义中间件,需要哪个密钥解析,.unless指定哪些接口不需要进行token身份认证
app.use(
jwt({ secret: config.jwtSecretKey, algorithms: ["HS256"] }).unless({
path: [/^\/api/],
})
);
// 导入并使用登录,注册的路由模块
const loginRouter = require("./router/login");
app.use("/api", loginRouter);
// 导入并使用路由模块
const inforRouter = require("./router/userInfor");
app.use(inforRouter);
// 在所有路由下面调用错误级别的中间件
app.use((err, req, res, next) => {
// 验证失败导致的错误
if (err instanceof joi.ValidationError) return res.output(err);
// 未知的错误
res.output(err);
next();
});
// 启动服务器
app.listen(3007, () => {
console.log("Server running at http://127.0.0.1:3007");
});
(1),在请求头里带了token,接口请求时要解析token,解析token需要一个包 express-jwt ,还需要和生成token时相同的密钥。
(2),在没有定义解析token的中间件时,请求这时所有的路由还是不需要token的,定义了之后,所有路由就都需要token了。unless()可以指定哪些路由不需要token。algorithms属性,设置jwt的算法,具体了解可以看 express-jwt 的文档。
(3),app.use("/api", loginRouter);使用路由时,前面加‘/api',在请求时,请求路径前也需要加 '/api',登录注册的时候不需要上送token。
未上送token,报错。
上送token,查询成功。
最后梳理一下整个过程中用到的包:
express:框架
mysql:数据库
@escook/express-joi:自动对表单数据进行验证
joi:字段规则
bcryptjs:对密码进行加密处理
jsonwebtoken:生成token
express-jwt:请求头上送token后解析token
边栏推荐
- 数据的同步为每个站点创建触发器同步表
- Period compression filter
- Method keywords deprecated, externalprocname, final, forcegenerate
- 【翻译】云原生观察能力微调查。普罗米修斯引领潮流,但要了解系统的健康状况仍有障碍...
- VMware virtual machine cannot open the kernel device "\.\global\vmx86"
- 语音识别(ASR)论文优选:全球最大的中英混合开源数据TALCS: An Open-Source Mandarin-English Code-Switching Corpus and a Speech
- Leetcode 30. Concatenate substrings of all words
- logstash高速入口
- MySQL must know and learn
- Cesium 两点之间的直线距离
猜你喜欢
Understand yolov1 Part II non maximum suppression (NMS) in prediction stage
It's enough to read this article to analyze the principle in depth
Standardized QCI characteristics
beegfs高可用模式探讨
腾讯T2大牛亲自讲解,跳槽薪资翻倍
系统与应用监控的思路和方法
腾讯安卓开发面试,android开发的基础知识
《数字经济全景白皮书》保险数字化篇 重磅发布
[translation] micro survey of cloud native observation ability. Prometheus leads the trend, but there are still obstacles to understanding the health of the system
Swiftui game source code Encyclopedia of Snake game based on geometryreader and preference
随机推荐
beegfs高可用模式探讨
PHP与EXCEL PHPExcel
Selenium advanced operations
Microservice architecture debate between radical technologists vs Project conservatives
精彩编码 【进制转换】
PowerPivot——DAX(初识)
Introduction to enterprise lean management system
HDU 1026 search pruning problem within the labyrinth of Ignatius and the prince I
Vscode debug run fluent message: there is no extension for debugging yaml. Should we find yaml extensions in the market?
Blue Bridge Cup microbial proliferation C language
Guangzhou's first data security summit will open in Baiyun District
JVM_常见【面试题】
Cesium 点击绘制圆形(动态绘制圆形)
理解 YOLOV1 第二篇 预测阶段 非极大值抑制(NMS)
Li Kou 101: symmetric binary tree
Cesium 两点之间的直线距离
POJ 3207 Ikki' s Story IV – Panda' s Trick (2-SAT)
腾讯架构师首发,2022Android面试笔试总结
Redisson bug analysis
Application of clock wheel in RPC