从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项目的用户鉴权功能,提供了一个可通用的鉴权模板。各位小伙伴可以根据自己的实际需求,创建项目需要的用户、角色、权限,完成自己项目的用户鉴权模块,为自己的项目添砖加瓦,打好基础。

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

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

相关推荐
yqcoder13 分钟前
Commander 一款命令行自定义命令依赖
前端·javascript·arcgis·node.js
stevewongbuaa31 分钟前
一些烦人的go设置 goland
开发语言·后端·golang
赵不困888(合作私信)3 小时前
npx和npm 和pnpm的区别
前端·npm·node.js
花心蝴蝶.4 小时前
Spring MVC 综合案例
java·后端·spring
落霞的思绪4 小时前
Redis实战(黑马点评)——关于缓存(缓存更新策略、缓存穿透、缓存雪崩、缓存击穿、Redis工具)
数据库·spring boot·redis·后端·缓存
m0_748255654 小时前
环境安装与配置:全面了解 Go 语言的安装与设置
开发语言·后端·golang
SomeB1oody9 小时前
【Rust自学】14.6. 安装二进制crate
开发语言·后端·rust
患得患失94911 小时前
【Django DRF Apps】【文件上传】【断点上传】从零搭建一个普通文件上传,断点续传的App应用
数据库·后端·django·sqlite·大文件上传·断点上传
customer0812 小时前
【开源免费】基于SpringBoot+Vue.JS校园失物招领系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
中國移动丶移不动13 小时前
Java 反射与动态代理:实践中的应用与陷阱
java·spring boot·后端·spring·mybatis·hibernate