【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (六):token的设置
本项目旨在学习如何快速使用 nodejs 开发后端api,并为以后开展其他项目的开启提供简易的后端模版。(非后端工程师)
由于文档是代码写完之后,为了记录项目中需要注意的技术点,因此文档的叙述方式并非开发顺序(并非循序渐进的教学文档)。建议配合项目源码node-mongodb-template 。
【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (一):项目简介及安装依赖
【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (二):项目文件夹架构及路由的设置
【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (三):Cors的设置及.env文件的设置
【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (四):状态码的使用
【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (五):POST上传文件的设置
【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (六):token的设置
【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (七):MongoDB的设置
【NodeJS】NodeJS+mongoDB在线版开发简单RestfulAPI (八):API说明(暂时完结,后续考虑将在线版mongoDB变为本地版)
token的设置
通过API查看数据时,不同的数据可能存在权限的要求,比如通常所有用户包括游客都可以查看products,但是只有登录的用户可以增加/删除/修改产品,只有登录的用户可以增删改查订单。
那么就需要设置用户登录的功能,用户登录成功之后,会在返回信息中提供token,程序就会校验token是否满足权限,然后在进行api请求。
实现
安装依赖
实现过程中需要两个依赖
pnpm i --save bcrypt
加密,用于用户密码的加密和校验
pnpm i --save jsonwebtoken
token, 用户生成和校验token
用户登录
用户登录的功能,需要用户类,可以注册用户,删除用户和登录。用户的密码需要进行加密,进行登录请求的时候需要生成token。
用户表设计
//models/user.js
const mongoose = require('mongoose');
const userSchema = mongoose.Schema({
_id:mongoose.Schema.Types.ObjectId,
email:{
type:String,
required:true,
unique:true,
match:/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
},
password:{type:String, required:true},
})
module.exports = mongoose.model('User', userSchema);
用户API设计
const express = require('express');
const router= express.Router();
const checkAuth = require('../middleware/check-auth');
const userController = require('../controllers/user')
router.post('/signup',userController.user_signup);
router.post('/login',userController.user_login);
router.delete('/:userId',checkAuth,userController.user_delete);
module.exports = router;
用户注册:加密
exports.user_signup = (req,res,next) => {
User.findOne({email: req.body.email})
.exec()
.then(user=>{
if(user){
res.status(409).json({
message:"Mail exists"
});
}else{
//密码加密
bcrypt.hash(req.body.password,10,(err,hash) => {
if(err){
return res.status(500).json({
err:err
})
}else{
const user = new User({
_id:new mongoose.Types.ObjectId(),
email:req.body.email,
password:hash
});
user
.save()
.then(result=>{
res.status(201).json({
message:'Create user successfully' ,
createdUser: {
result: result,
}
});
})
.catch(err=>{
res.status(500).json({
error:err
});
});
}
})
}
})
.catch(err=>{
res.status(500).json({
error:err
});
});
};
用户登录: 校验密码、生成token
exports.user_login = (req,res,next) => {
User.find({email: req.body.email})
.exec()
.then(user=>{
if(user.length<1){
res.status(401).json({
message:"Auth Failed"
});
}else{
//检查密码是否正确
bcrypt.compare(req.body.password,user[0].password,(err,result) => {
if(err){
res.status(401).json({
message:"Auth Failed"
});
}
if(result){
//生产token
const token = jwt.sign({
email:user[0].email,
userId:user[0]._id
},process.env.JWT_KEY,{
expiresIn:"1h",
});
res.status(200).json({
message:"Auth successfully",
token:token,
request:{
type: 'POST',
url: 'http://localhost:3000/user/signup',
body:{
email:"string",
password:"string"
}
}
});
}else{
res.status(401).json({
message:"Auth Failed"
});
}
})
}
})
.catch(err=>{
res.status(500).json({
error:err
});
});
};
用户删除: 校验token
exports.user_delete = (req,res,next) => {
const id = req.params.userId;
User.deleteOne({_id:id})
.exec()
.then(result=>{
res.status(200).json({
message:"User deleted successfully",
request:{
type: 'POST',
url: 'http://localhost:3000/user/signup',
body:{
email:"string",
password:"string"
}
}
});
})
.catch(err=>{
res.status(500).json({
error:err
});
});
};
校验token
由于多处需要校验用户是否登录,即校验token。因此应该将校验token的部分单独作为一个中间件,在api请求时,进行使用。
中间件
新建文件夹middleware
,然后新建文件check-auth.js
const jwt = require('jsonwebtoken');
module.exports = (req,res,next)=>{
try{
const token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token,process.env.JWT_KEY);
req.userData = decoded;
next();
}
catch(error){
return res.status(401).json({
message:'Auth failed'
});
}
};
api请求前校验token
这里以前文中用户删除为例,用户的删除操作只能是用户登录的状态下进行,因此需要进行校验。
可以看到如下代码
const checkAuth = require('../middleware/check-auth');
//校验的中间件写在请求中。
router.delete('/:userId',checkAuth,userController.user_delete);
如果遇到既需要校验,有需要上传文件的操作是,校验要写在上传文件中间件之前,如增加一个产品。
router.post('/',checkAuth,upload.single('productImage'),productsController.create_product);
postman中输入token进行请求
注册用户
POST localhost:3000/user/signup
Body
如下
{
"email":"123@test.com",
"password":"0000000"
}
登录用户
POST localhost:3000/user/login
Body
如下
{
"email":"123@test.com",
"password":"0000000"
}
请求成功之后,返回如下
{
"message": "Auth successfully",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IjEyM0B0ZXN0LmNvbSIsInVzZXJJZCI6IjY2ZTA1NGY5YTM1ZmM4OGQ0YTRhNjY5ZSIsImlhdCI6MTcyNjIzODI2OCwiZXhwIjoxNzI2MjQxODY4fQ.W8_9mmBLU4IzcycEh6sNO95wlepp6qQJToOdM9LgEQ4",
"request": {
"type": "POST",
"url": "http://localhost:3000/user/signup",
"body": {
"email": "string",
"password": "string"
}
}
}
其中包括 token
的值,复制
查询订单
GET localhost:3000/orders
在Header
中新增一个属性 Authorization
, 并将值输入为如下 Bearer [token]
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6IjEyM0B0ZXN0LmNvbSIsInVzZXJJZCI6IjY2ZTA1NGY5YTM1ZmM4OGQ0YTRhNjY5ZSIsImlhdCI6MTcyNjIzODI2OCwiZXhwIjoxNzI2MjQxODY4fQ.W8_9mmBLU4IzcycEh6sNO95wlepp6qQJToOdM9LgEQ4
然后点击发送请求,此时就可以请求成功了。