从Express开始学习node.js(六)实现用户鉴权
引言
Hello,小伙伴们好,好久不见,我又回来了。很感谢大家对本专栏的认可,以及小伙伴们的催更,本专栏正式回归啦 !
在后续的内容中,我将和大家一起实现一些后端常见的场景(也就是大家说的后端最佳实践),如RBAC用户鉴权。此外,我还会找一个章节,专门给大家介绍一下如何在服务器(以云主机为例)上部署我们的后台系统,脱离本机环境,搭建一个可外部访问的后台服务。
通过前几个章节,我们已经学习了express的大部分知识,在本地搭建了一个基础的MySQL服务,并且用express提供了数据库查询的简单功能。承接本专栏的上一篇文章从Express开始学习node.js(五)接入数据库,本文将实现项目的用户鉴权
功能,为后续的接口开发打好基础。
本篇文章作为node.js系列的第六章节,主要内容有:
-
项目前期准备
-
ORM与Sequelize
-
RBAC鉴权
-
JWT
-
利用Sequelize实现RBAC鉴权
注:本章节所有的内容,都需要node.js环境,请确保自己已安装node环境
一、项目前期准备
在我们之前5个章节的学习中,主要目标为学习及测试,项目能够按照我们的预期进行即可,然而在实际开发工作中,我们现在的项目还存在以下问题:
- 项目代码每次改动都需要重新启动才能生效
- 几乎所有的代码都存放在app.js这一个文件中
- 存在跨域问题
- 缺少日志输出
- 有部分中间件已经过时,有更好的代替方案
为了能够更加高效地开发与维护,我们一起来解决这些问题。
1、使用nodemon来启动项目
Nodemon
是一个 Node.js 开发工具,用于在开发过程中自动监测文件变化,并自动重启 Node.js 应用程序,可以让我们在编写代码时不需要手动重启服务器,从而节省了时间并提高了开发效率。
- 安装nodemon
shell
npm install nodemon --dev
- 在package.json中修改启动命令
shell
"scripts": {
"dev": "nodemon app.js"
},
- 修改nodemon配置项,精细控制
2、整理目录结构
整理后的项目目录为:
yaml
- api
- controllers
- models
- routes
- config
db.config.js
- files
- log
- middlewares
- public
- utils
- views
- app.js
- package.json
- README.md
我们将接口相关的部分都放在api目录下,主要包括routes、models、controllers三个部分。
3、使用cors
解决跨域问题
CORS
(Cross-Origin Resource Sharing,跨源资源共享)是一个浏览器安全机制,它允许或限制网页(运行在浏览器中)如何与不同源(域名、协议或端口)的服务器进行交互。我们在前端请求时,跨域是一个很常见的问题,在 Node.js 应用中,cors模块用来解决跨域问题。
- 安装cors
shell
npm install cors
- 在index.js中使用
cors
中间件
js
app.use(cors())
4、使用winston进行日志管理
Winston
是一个Node.js 开源日志库,让我们更容易地对日志进行管理,以便在出现问题时排查原因。
- 安装winston
shell
npm install winston
- 在
src/utils
目录下添加log.js
,完成winston配置
js
// src/utis/log,js
import winston from 'winston';
const logger = winston.createLogger({
level: 'debug',
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple(),
),
}),
new winston.transports.File({
dirname: 'log',
filename: 'test.log',
maxsize: 1024,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
)
}),
]
});
export default logger;
- 在需要记录日志的地方引入logger并使用logger记录日志
js
// 引入logger
const logger = require('./logger');
// 记录日志
logger.error('This is an error message');
5、数据处理中间件
我们前面用了一个名为body-parser
的中间件来处理数据,但express已经不建议使用了,我们直接使用express提供的数据处理中间件。
- 我们先卸载掉body-parse
shell
npm uninstall body-parse
- 在
src/index.js
中使用express提供的中间件
js
// src/index.js
// 解析表单中 application/x-www-form-urlencoded 格式的数据
app.use(express.urlencoded({ extended: false }));
// 解析表单中application/json格式的数据
app.use(express.json());
// 解析表单中text/plain格式的数据
app.use(express.text());
二、ORM与Sequelize
在上一章节中,我们完成了一个"获取所有用户"的简单功能,代码如下:
js
// index.js
app.get('/users', (req, res) => {
db.query('select * from user', (err, users) => {
if (err) throw err
res.json(users);
})
})
我们在查询用户列表时,采用的是原始的SQL语句
,这种方式不易于开发与维护,存在的问题有且不止于:
- 原始SQL语句代码不易理解
- 原始的SQL语句与底层数据库相关联,对于不同的数据库需要写不同的SQL语句
- 大量的重复代码,难以维护
- 联表查询和子查询等复杂查询的SQL语句难以编写与修改
- 对象的关系映射不易于处理
为了解决这些问题,使数据库交互更加直观和简洁,ORM出现了。
ORM
(Object-Relational Mapping,对象关系映射)是一种程序设计技术,用于在关系数据库和对象程序语言之间转换数据。它允许开发者使用面向对象的方式来处理数据库操作,而不是直接使用数据库查询语言(如 SQL)。
Sequelize
是一个Node.js的ORM库,我们可以通过定义模型(Model)的方式,以面向对象的方式描述数据库中的表结构和关系。同时,可以使用Sequelize提供的一系列方法来执行 CRUD(创建、读取、更新、删除)操作,以及更复杂的查询,如联表查询、聚合等。
在后续的开发中,我们将主要使用Sequelize这一ORM库来操作数据库,对我们的后台系统进行CRUD。
三、RBAC鉴权
RBAC
(Role-Based Access Control,基于角色的访问控制)是一种广泛使用的访问控制策略,它通过定义角色和权限来管理用户对系统资源的访问。在 RBAC 模型中,权限不是直接分配给单个用户,而是分配给角色,然后用户被分配到一个或多个角色。这样,用户通过他们的角色间接获得权限。
我们之所以使用RBAC鉴权,而不是普通的isAdmin,有以下原因:
- RBAC具有更好的灵活性和可扩展性,可以有多种角色
- 当用户的职责变化时,只需更改他们的角色分配,而不需要逐一更新他们的权限设置
- 可以实现对权限更精细的管理,比如:只读、可写等权限
与RBAC相关的核心表有:
- 用户表(users) :存储用户信息
- 角色表(roles) :存储角色信息。
- 权限表(permissions) :存储权限信息。
- 用户角色关联表(user_roles) :存储用户和角色的关系。
- 角色权限关联表(role_permissions) :存储角色和权限的关系。
四、JWT
JWT
(JSON Web Tokens)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。
在Web开发中,JWT通常用于实现用户认证和授权。基本流程如下:
- 用户在客户端使用账号/密码登录
- 服务器验证登录信息
- 如果验证成功,服务器创建一个包含用户信息(如用户ID、角色等)的JWT,并使用一个密钥对其进行签名。
- 服务器将签名后的JWT返回给用户。
- 客户端存储JWT,在后续的请求中将JWT放在HTTP请求的Authorization头部中,通常是以Bearer模式:
Authorization: Bearer <token>
- 服务器接收到请求后,从Authorization头部提取JWT。
- 服务器对JWT进行解码,验证签名是否有效。
- 如果签名有效,服务器读取JWT中的信息,如用户ID和角色等。
- 服务器根据JWT中的信息允许或拒绝用户的请求。
- 处理JWT的过期与刷新机制。
五、代码实现
1、在MySQL数据库中建立RBAC相关的核心表
其实这一步大家
可以跳过
。我们只需要在sequelize中定义好模型,在初始化时,如果检测到没有对应的表,sequelize会在数据库中根据定义好的模型自动创建
表。这也是我为什么推荐使用Sequelize的原因之一,它可以让我们忽略那些繁琐的建表、查询、插入数据等命令,集中专注在业务实际场景需要上。
- 用户表(users) :存储用户信息
mysql
CREATE TABLE `users` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(255) NOT NULL,
`password` VARCHAR(255) NOT NULL,
`email` VARCHAR(255),
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE INDEX `username_UNIQUE` (`username` ASC),
UNIQUE INDEX `email_UNIQUE` (`email` ASC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 角色表(roles) :存储角色信息。
mysql
CREATE TABLE `roles` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE INDEX `name_UNIQUE` (`name` ASC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 权限表(permissions) :存储权限信息。
mysql
CREATE TABLE `permissions` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE INDEX `name_UNIQUE` (`name` ASC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 用户角色关联表(user_roles) :存储用户和角色的关系。
mysql
CREATE TABLE `user_roles` (
`user_id` INT NOT NULL,
`role_id` INT NOT NULL,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`, `role_id`),
INDEX `fk_user_roles_role_idx` (`role_id` ASC),
CONSTRAINT `fk_user_roles_user`
FOREIGN KEY (`user_id`)
REFERENCES `users` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION,
CONSTRAINT `fk_user_roles_role`
FOREIGN KEY (`role_id`)
REFERENCES `roles` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 角色权限关联表(role_permissions) :存储角色和权限的关系。
mysql
CREATE TABLE `role_permissions` (
`role_id` INT NOT NULL,
`permission_id` INT NOT NULL,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`role_id`, `permission_id`),
INDEX `fk_role_permissions_permission_idx` (`permission_id` ASC),
CONSTRAINT `fk_role_permissions_role`
FOREIGN KEY (`role_id`)
REFERENCES `roles` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION,
CONSTRAINT `fk_role_permissions_permission`
FOREIGN KEY (`permission_id`)
REFERENCES `permissions` (`id`)
ON DELETE CASCADE
ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2、初始化角色和权限
- 初始化角色
mysql
INSERT INTO `roles` (`name`) VALUES ('ordinary_employees');
INSERT INTO `roles` (`name`) VALUES ('project_manager');
INSERT INTO `roles` (`name`) VALUES ('admin');
- 初始化权限
mysql
INSERT INTO `permissions` (`name`) VALUES ('view_user');
INSERT INTO `permissions` (`name`) VALUES ('create_user');
INSERT INTO `permissions` (`name`) VALUES ('update_user');
INSERT INTO `permissions` (`name`) VALUES ('delete_user');
INSERT INTO `permissions` (`name`) VALUES ('view_role');
INSERT INTO `permissions` (`name`) VALUES ('create_role');
INSERT INTO `permissions` (`name`) VALUES ('update_role');
INSERT INTO `permissions` (`name`) VALUES ('delete_role');
INSERT INTO `permissions` (`name`) VALUES ('view_permission');
INSERT INTO `permissions` (`name`) VALUES ('create_permission');
INSERT INTO `permissions` (`name`) VALUES ('update_permission');
INSERT INTO `permissions` (`name`) VALUES ('delete_permission');
- 为admin角色分配与鉴权相关的权限
mysql
INSERT INTO `role_permissions` (`role_id`, `permission_id`) VALUES (3, 1);
INSERT INTO `role_permissions` (`role_id`, `permission_id`) VALUES (3, 2);
...
INSERT INTO `role_permissions` (`role_id`, `permission_id`) VALUES (3, 12);
- 创建admin用户并为其分配admin角色
mysql
INSERT INTO `users` (`username`, `password`) VALUES ('admin', '123456');
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (1, 3);
- 测试验证
mysql
select * from users;
select * from user_roles where user_id = 1;
select * from role_permissions where role_id = 3;
3、使用Sequelize编写api接口
- 安装Sequelize依赖
bash
npm install sequelize
- 在models中定义模型,模型的结构与我们创建的表结构一致
js
// api/models/user.js
module.exports = (sequelize, Sequelize) => {
const User = sequelize.define("User", {
username: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
password: {
type: Sequelize.STRING,
allowNull: false,
},
email: {
type: Sequelize.STRING,
defaultValue: null
},
}, {
tableName: 'users'
});
return User;
};
js
// api/models/Role.js
module.exports = (sequelize, Sequelize) => {
const Role = sequelize.define('Role', {
name: {
type: Sequelize.STRING(255),
allowNull: false,
unique: true
},
}, {
// 模型的额外选项
tableName: 'roles'
});
return Role;
};
js
// api/models/Permission.js
module.exports = (sequelize, Sequelize) => {
const Permission = sequelize.define('Permission', {
name: {
type: Sequelize.STRING(255),
allowNull: false,
unique: true
},
}, {
// 模型的额外选项
tableName: 'permissions'
});
return Permission;
};
js
// api/models/UserRole.js
import { Sequelize, sequelize } from ".";
const UserRole = sequelize.define('user_role', {
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'users', // 指定参照的模型名
key: 'id' // 指定参照模型的键名
},
onDelete: 'CASCADE', // 删除用户时级联删除关联
onUpdate: 'NO ACTION' // 更新用户ID时不采取任何操作
},
role_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'roles',
key: 'id'
},
onDelete: 'CASCADE', // 删除角色时级联删除关联
onUpdate: 'NO ACTION' // 更新角色ID时不采取任何操作
},
}, {
tableName: 'user_roles',
// 其他模型选项
});
module.exports = UserRole;
js
// api/models/RolePermission.js
const { Sequelize, sequelize } = require("./index");
const RolePermission = sequelize.define('role_permission', {
role_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'roles',
key: 'id'
},
onDelete: 'CASCADE', // 删除角色时级联删除关联
onUpdate: 'NO ACTION' // 更新角色ID时不采取任何操作
},
permission_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'permissions',
key: 'id'
},
onDelete: 'CASCADE', // 删除权限时级联删除关联
onUpdate: 'NO ACTION' // 更新权限ID时不采取任何操作
},
}, {
tableName: 'role_permissions',
// 其他模型选项
});
module.exports = RolePermission;
js
// api/models/index.js
const dbConfig = require("../../config/db.config.js");
const Sequelize = require("sequelize");
const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {
host: dbConfig.HOST,
dialect: dbConfig.dialect,
operatorsAliases: false,
pool: {
max: dbConfig.pool.max,
min: dbConfig.pool.min,
acquire: dbConfig.pool.acquire,
idle: dbConfig.pool.idle
}
});
const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.User = require("./User.js")(sequelize, Sequelize);
db.Role = require("./Role.js")(sequelize, Sequelize);
db.Permission = require("./Permission.js")(sequelize, Sequelize);
// 设置关联关系
db.User.belongsToMany(db.Role, { through: 'user_roles', foreignKey: 'user_id', otherKey: 'role_id' });
db.Role.belongsToMany(db.User, { through: 'user_roles', foreignKey: 'role_id', otherKey: 'user_id' });
db.Role.belongsToMany(db.Permission, { through: 'role_permissions', foreignKey: 'role_id', otherKey: 'permission_id' });
db.Permission.belongsToMany(db.Role, { through: 'role_permissions', foreignKey: 'permission_id', otherKey: 'role_id' });
// 获取用户权限列表
db.User.prototype.getPermissions = async function () {
const roles = await this.getRoles();
const permissions = await Promise.all(roles.map(role => role.getPermissions()));
return permissions.flat(); // 扁平化权限数组
};
module.exports = db;
- 在controllers中编写CRUD,对数据库进行操作
js
// api/controllers/userControllers.js
const bcrypt = require('bcryptjs');
const { jwtSign, jwtDecode } = require('../../utils/jwt.js');
const db = require("../models");
const User = db.User;
const Op = db.Sequelize.Op;
const login = async (req, res) => {
const { username, password } = req.body;
try {
const user = await User.findOne({ where: { username } });
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
const hash = await bcrypt.hash(password, 10);
const isPasswordValid = bcrypt.compareSync(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({ accessToken: null, message: '无效的密码' });
}
const token = 'Bearer ' + jwtSign({ id: user.id, username });
res.status(200).json({ token: token });
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
};
// 创建新用户
const createUser = async (req, res) => {
const { username, password, email } = req.body;
try {
const hashedPassword = bcrypt.hashSync(password, 8);
const newUser = await User.create({ username, password: hashedPassword, email });
res.status(201).json(newUser);
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
};
const getUsers = async (req, res) => {
const { limit, current, orderBy, sortOrder, ...filters } = req.query;
const params = {};
// 分页
if (limit && current) {
params.limit = limit ? parseInt(limit) : 10; // 默认每页10条
params.offset = (current - 1) * (params.limit); // 计算跳过的记录数
}
// 排序
if (orderBy) {
params.order = [[orderBy, sortOrder || "desc"]]; // 默认降序排序
}
// 模糊搜索
const where = {};
for (const key in filters) {
if (filters[key]) {
where[key] = {
[Op.like]: `%${filters[key]}%`
};
}
}
params.where = where;
// 指定返回字段
params.attributes = { exclude: ['password'] }; // 排除敏感信息
try {
const data = await User.findAndCountAll(params);
// 构造分页信息
const pagination = {
total: data.count,
current: current ? parseInt(current) : 1,
pageSize: params.limit,
};
// 将分页信息和用户数据一起返回
res.status(200).json({
list: data.rows,
pagination,
});
} catch (err) {
res.status(500).send({
message: err.message || "查询失败",
});
}
};
// 查询单个用户详情
const getUserById = async (req, res) => {
const { id } = req.params;
try {
const user = await User.findByPk(id, {
attributes: { exclude: ['password'] } // 排除敏感信息
});
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
res.status(200).json(user);
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
};
// 删除某个用户
const deleteUser = async (req, res) => {
const { id } = req.params;
try {
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
await user.destroy();
res.status(204).send(); // 成功删除,返回204 No Content
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
};
// 修改用户信息
const updateUser = async (req, res) => {
const { id } = req.params;
const { username, email } = req.body;
try {
const user = await User.findByPk(id);
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
// 更新用户信息
user.username = username || user.username;
user.email = email || user.email;
await user.save();
res.status(200).json(user);
} catch (error) {
res.status(500).json({ message: '服务器错误' });
}
};
module.exports = {
login,
createUser,
getUsers,
getUserById,
deleteUser,
updateUser
};
- 编写鉴权相关逻辑
jwt令牌验证:
js
// middlewares/authenticateToken.js
const jwt = require('jsonwebtoken');
const { SECRET_KEY } = require("../utils/constant.js");
const db = require("../api/models");
const User = db.User;
function authenticateToken(req, res, next) {
// 从请求头中获取认证信息
const authHeader = req.headers['authorization'];
// 从认证信息中提取令牌
const token = authHeader && authHeader.split(' ')[1];
// 如果令牌为空,则返回401状态码
if (token == null) return res.sendStatus(401);
// 验证令牌
jwt.verify(token, SECRET_KEY, async (err, decoded) => {
// 如果验证失败,则返回403状态码
if (err) return res.sendStatus(403);
try {
const user = await User.findByPk(decoded.id);
req.user = user; // 将用户实例赋值给req.user
next();
} catch (error) {
console.log(error);
res.status(500).send('服务器错误');
}
});
}
module.exports = authenticateToken;
用户权限校验:
js
// middlewares/checkPermission.js
function checkPermission(requiredPermission) {
return async (req, res, next) => { // 这是一个异步中间件函数
try {
const permissions = await req.user.getPermissions(); // 异步获取权限列表
const hasPermission = permissions.some(p => p.name === requiredPermission);
if (!hasPermission) return res.status(403).json({ message: '权限不足' });
next(); // 有权限,继续执行下一个中间件或路由处理器
} catch (error) {
console.error(error);
res.status(500).json({ message: '服务器错误' });
}
};
}
module.exports = checkPermission;
权限列表:
js
// utils/permissions
// 用户相关权限
const PERMISSION_VIEW_USER = 'view_user';
const PERMISSION_CREATE_USER = 'create_user';
const PERMISSION_UPDATE_USER = 'update_user';
const PERMISSION_DELETE_USER = 'delete_user';
// 角色相关权限
const PERMISSION_VIEW_ROLE = 'view_role';
const PERMISSION_CREATE_ROLE = 'create_role';
const PERMISSION_UPDATE_ROLE = 'update_role';
const PERMISSION_DELETE_ROLE = 'delete_role';
// 权限相关权限
const PERMISSION_VIEW_PERMISSION = 'view_permission';
const PERMISSION_CREATE_PERMISSION = 'create_permission';
const PERMISSION_UPDATE_PERMISSION = 'update_permission';
const PERMISSION_DELETE_PERMISSION = 'delete_permission';
// 导出所有权限常量
module.exports = {
PERMISSION_VIEW_USER,
PERMISSION_CREATE_USER,
PERMISSION_UPDATE_USER,
PERMISSION_DELETE_USER,
PERMISSION_VIEW_ROLE,
PERMISSION_CREATE_ROLE,
PERMISSION_UPDATE_ROLE,
PERMISSION_DELETE_ROLE,
PERMISSION_VIEW_PERMISSION,
PERMISSION_CREATE_PERMISSION,
PERMISSION_UPDATE_PERMISSION,
PERMISSION_DELETE_PERMISSION,
};
- 在routes中定义路由
js
// api/routes/user.js
const express = require("express");
const router = express.Router();
const Controller = require("../controllers/userController");
const checkPermission = require("../../middlewares/checkPermission");
const authenticateToken = require("../../middlewares/authenticateToken");
const permissions = require("../../utils/permissions");
router.post('/login', Controller.login);
router.post('/',authenticateToken,checkPermission(permissions.PERMISSION_CREATE_USER),Controller.createUser);
router.get('/', authenticateToken, checkPermission(permissions.PERMISSION_VIEW_USER), Controller.getUsers);
router.get('/:id', authenticateToken, Controller.getUserById);
router.delete('/:id', authenticateToken, checkPermission(permissions.PERMISSION_DELETE_USER), Controller.deleteUser);
router.put('/:id', authenticateToken, checkPermission(permissions.PERMISSION_UPDATE_USER), Controller.updateUser);
module.exports = router;
- 在app.js中使用定义好的user路由,并同步数据库模型
js
...
const db = require("./api/models");
const usersRouter = require("./api/routes/user.js");
...
// 使用定义好的user路由
app.use("/user", usersRouter);
...
// 同步数据库模型,如果数据库中没有对应表,在这一步会自动创建表
db.sequelize.sync().then(() => {
console.log("Synced db.");
}).catch((err) => {
console.log("Failed to sync db: " + err.message);
});
- 使用postman测试接口
现在,来请求一下我们定义好的/user的路由,我们可以发现API接口已经完成了:
-
POST /user/login
-
GET /user
-
Post /user
总结
在这一节,我们对整体的项目结构进行了优化与调整,学习了ORM与Sequelize、RBAC鉴权、JWT等核心内容,并利用Sequelize实现了RBAC鉴权相关的API接口。
至此,我们已经实现了一个Express项目的用户鉴权功能,提供了一个可通用的鉴权模板。各位小伙伴可以根据自己的实际需求,创建项目需要的用户、角色、权限,完成自己项目的用户鉴权模块,为自己的项目添砖加瓦,打好基础。
我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!
如有问题,欢迎在留言区一起讨论。