从Express开始学习node.js(六)实现用户鉴权功能

从Express开始学习node.js(六)实现用户鉴权

引言

Hello,小伙伴们好,好久不见,我又回来了。很感谢大家对本专栏的认可,以及小伙伴们的催更,本专栏正式回归啦 !

在后续的内容中,我将和大家一起实现一些后端常见的场景(也就是大家说的后端最佳实践),如RBAC用户鉴权。此外,我还会找一个章节,专门给大家介绍一下如何在服务器(以云主机为例)上部署我们的后台系统,脱离本机环境,搭建一个可外部访问的后台服务。

通过前几个章节,我们已经学习了express的大部分知识,在本地搭建了一个基础的MySQL服务,并且用express提供了数据库查询的简单功能。承接本专栏的上一篇文章从Express开始学习node.js(五)接入数据库,本文将实现项目的用户鉴权功能,为后续的接口开发打好基础。

本篇文章作为node.js系列的第六章节,主要内容有:

  • 项目前期准备

  • ORM与Sequelize

  • RBAC鉴权

  • JWT

  • 利用Sequelize实现RBAC鉴权

注:本章节所有的内容,都需要node.js环境,请确保自己已安装node环境

一、项目前期准备

在我们之前5个章节的学习中,主要目标为学习及测试,项目能够按照我们的预期进行即可,然而在实际开发工作中,我们现在的项目还存在以下问题:

  1. 项目代码每次改动都需要重新启动才能生效
  2. 几乎所有的代码都存放在app.js这一个文件中
  3. 存在跨域问题
  4. 缺少日志输出
  5. 有部分中间件已经过时,有更好的代替方案

为了能够更加高效地开发与维护,我们一起来解决这些问题。

1、使用nodemon来启动项目

Nodemon 是一个 Node.js 开发工具,用于在开发过程中自动监测文件变化,并自动重启 Node.js 应用程序,可以让我们在编写代码时不需要手动重启服务器,从而节省了时间并提高了开发效率。

  1. 安装nodemon
shell 复制代码
npm install nodemon --dev
  1. 在package.json中修改启动命令
shell 复制代码
 "scripts": {
    "dev": "nodemon app.js"
  },
  1. 修改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模块用来解决跨域问题。

  1. 安装cors
shell 复制代码
npm install cors
  1. 在index.js中使用cors中间件
js 复制代码
app.use(cors())

4、使用winston进行日志管理

Winston 是一个Node.js 开源日志库,让我们更容易地对日志进行管理,以便在出现问题时排查原因。

  1. 安装winston
shell 复制代码
npm install winston
  1. 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;
  1. 在需要记录日志的地方引入logger并使用logger记录日志
js 复制代码
// 引入logger
const logger = require('./logger');

// 记录日志
logger.error('This is an error message');

5、数据处理中间件

我们前面用了一个名为body-parser的中间件来处理数据,但express已经不建议使用了,我们直接使用express提供的数据处理中间件。

  1. 我们先卸载掉body-parse
shell 复制代码
npm uninstall body-parse
  1. 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相关的核心表有:

  1. 用户表(users) :存储用户信息
  2. 角色表(roles) :存储角色信息。
  3. 权限表(permissions) :存储权限信息。
  4. 用户角色关联表(user_roles) :存储用户和角色的关系。
  5. 角色权限关联表(role_permissions) :存储角色和权限的关系。

四、JWT

JWT(JSON Web Tokens)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象的形式安全地传输信息。

在Web开发中,JWT通常用于实现用户认证和授权。基本流程如下:

  1. 用户在客户端使用账号/密码登录
  2. 服务器验证登录信息
  3. 如果验证成功,服务器创建一个包含用户信息(如用户ID、角色等)的JWT,并使用一个密钥对其进行签名。
  4. 服务器将签名后的JWT返回给用户。
  5. 客户端存储JWT,在后续的请求中将JWT放在HTTP请求的Authorization头部中,通常是以Bearer模式:Authorization: Bearer <token>
  6. 服务器接收到请求后,从Authorization头部提取JWT。
  7. 服务器对JWT进行解码,验证签名是否有效。
  8. 如果签名有效,服务器读取JWT中的信息,如用户ID和角色等。
  9. 服务器根据JWT中的信息允许或拒绝用户的请求。
  10. 处理JWT的过期与刷新机制。

五、代码实现

1、在MySQL数据库中建立RBAC相关的核心表

其实这一步大家可以跳过。我们只需要在sequelize中定义好模型,在初始化时,如果检测到没有对应的表,sequelize会在数据库中根据定义好的模型自动创建表。这也是我为什么推荐使用Sequelize的原因之一,它可以让我们忽略那些繁琐的建表、查询、插入数据等命令,集中专注在业务实际场景需要上。

  1. 用户表(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;
  1. 角色表(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;
  1. 权限表(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;
  1. 用户角色关联表(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;
  1. 角色权限关联表(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、初始化角色和权限

  1. 初始化角色
mysql 复制代码
INSERT INTO `roles` (`name`) VALUES ('ordinary_employees');
INSERT INTO `roles` (`name`) VALUES ('project_manager');
INSERT INTO `roles` (`name`) VALUES ('admin');
  1. 初始化权限
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');
  1. 为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);
  1. 创建admin用户并为其分配admin角色
mysql 复制代码
INSERT INTO `users` (`username`, `password`) VALUES ('admin', '123456');

INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (1, 3); 
  1. 测试验证
mysql 复制代码
select * from users;

select * from user_roles where user_id = 1;

select * from role_permissions where role_id = 3;

3、使用Sequelize编写api接口

  1. 安装Sequelize依赖
bash 复制代码
npm install sequelize
  1. 在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;
  1. 在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
};
  1. 编写鉴权相关逻辑

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,
};
  1. 在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;
  1. 在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);
});
  1. 使用postman测试接口

现在,来请求一下我们定义好的/user的路由,我们可以发现API接口已经完成了:

  • POST /user/login

  • GET /user

  • Post /user

总结

在这一节,我们对整体的项目结构进行了优化与调整,学习了ORM与Sequelize、RBAC鉴权、JWT等核心内容,并利用Sequelize实现了RBAC鉴权相关的API接口。

至此,我们已经实现了一个Express项目的用户鉴权功能,提供了一个可通用的鉴权模板。各位小伙伴可以根据自己的实际需求,创建项目需要的用户、角色、权限,完成自己项目的用户鉴权模块,为自己的项目添砖加瓦,打好基础。

我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!

如有问题,欢迎在留言区一起讨论。

相关推荐
yuuki23323316 分钟前
【C语言】文件操作(附源码与图片)
c语言·后端
IT_陈寒20 分钟前
Python+AI实战:用LangChain构建智能问答系统的5个核心技巧
前端·人工智能·后端
无名之辈J43 分钟前
系统崩溃(OOM)
后端
码农刚子1 小时前
ASP.NET Core Blazor简介和快速入门 二(组件基础)
javascript·后端
间彧1 小时前
Java ConcurrentHashMap如何合理指定初始容量
后端
catchadmin1 小时前
PHP8.5 的新 URI 扩展
开发语言·后端·php
少妇的美梦1 小时前
Maven Profile 教程
后端·maven
白衣鸽子1 小时前
RPO 与 RTO:分布式系统容灾的双子星
后端·架构
Jagger_1 小时前
SOLID原则与设计模式关系详解
后端
间彧1 小时前
Java: HashMap底层源码实现详解
后端