当前位置: 首页 > news >正文

RBAC 权限控制:深入到按钮级别的实现

RBAC 权限控制:深入到按钮级别的实现

一、前端核心思路

1. 大致实现思路

后端都过SELECT连表查询把当前登录的用户对应所有的权限返回过来,前端把用户对应所有的权限 存起来to(vuex/pinia) 中 ,接着前端工程师需要知道每个按钮对应的权限代号如:
在这里插入图片描述

。至此为止,我们即知道了用户对应所有的权限也知道了每个权限的名字(或代号),现在我们可以从前端的层面区控制某个权限按钮的显示与否如:

	<a-button
      type="primary"
      v-if="userPermiss_vuex.includes('user_add')"
      @click="add_user()"
      >添加用户</a-button
    >
	<!-- userPermiss_vuex:存储在vuex中的用户对应所有的权限 -->

2. 具体实现

首先在用户登录成功的时候调用获取用户所以权限的方法,方法如下:

// 获取用户对应的权限
const userPermiss = async () => {
  try {
    const results = await http.get("/api/users/userPermiss")
    const userPermiss = results.data.results
    // console.log('获取用户对应的权限::',results.data)
    // 将用户权限存到vuex中
    store.commit("SET_PERMISS", userPermiss);
  } catch (error) {
    console.log(error);
    message.error(error.response.data.message);
  }
};

后端运用到的数据表:在这里插入图片描述

对应的后端接口代码:

// model/user.js
static async userPermiss(userId) {  
        try {
            const sql = `
                SELECT 
                    Roles.id, 
                    Roles.name, 
                    Permissions.name as permission_name, 
                    Permissions.permission_code
                FROM UserRoles
                JOIN Roles ON UserRoles.role_id = Roles.id
                JOIN RolePermissions ON Roles.id = RolePermissions.role_id
                JOIN Permissions ON RolePermissions.permission_id = Permissions.id
                WHERE UserRoles.user_id = ?
            `;
            
            const roleRows = await query(sql, [userId]);
            
            if (!roleRows || roleRows.length === 0) {
                return []; // 返回空数组而不是undefined或null
            }
            
            const userPermissions = roleRows.map(role => role.permission_code);
            return [...new Set(userPermissions)]; // 去重
        } catch (error) {
            console.error('获取用户权限失败:', error);
            throw error; // 或者返回空数组: return [];
        }
    }

在具体页面中控制权限按钮的显示与隐藏:

方式一:传统的方式:

	<a-button
      type="primary"
      v-if="userPermiss_vuex.includes('user_add')"
      @click="add_user()"
      >添加用户</a-button
    >

<script setup>
import { useStore } from "vuex";
const store = useStore();
// 用户对应的权限名
const userPermiss_vuex = store.state.userPermiss;
</script>

方式二:自定义指令的方式(推荐):
vue中自定义指令问题:
在这里插入图片描述

<template>
	<a-button
      type="primary"
      @click="add_user()"
      >添加用户</a-button
    >
    <a-button
      type="primary"
      danger
      v-checkpermission:foo.bar="'user_delete'"
      @click="delete_user()"
      >删除用户</a-button
    >
</template>

<script setup>
// 创建一个自定义指令,来统一处理权限存在与否
const vCheckpermission = {
  mounted: (el, binding) => {
    if (!userPermiss_vuex.includes(binding.value)) {
      // 移除元素
      el.parentNode?.removeChild(el);
    }
    console.log(el, binding);
    // el.classList.add("is-highlight");
  },
};
</script>

二、后端核心思路

当然了,只在前端去实现权限的控制肯定是不安全的,还需要在后端请求接口的时候进行无权限阻止访问的操作,这里就需要运用到中间件了,下面只讲解了加入权限限制的代码。

1. 首先,我们创建一个中间件文件夹middleware

2. 接着,在middleware下创建 js 文件 auth.js ,代码如下:

// 引入jsonwebtoken模块,用于处理JWT(JSON Web Token)相关操作
const jwt = require('jsonwebtoken');

// 定义一个名为auth的中间件函数,用于验证请求的认证信息
const auth = (req, res, next) => {
  // 打印请求头中的Authorization字段,用于调试查看是否有token传递过来
  console.log(req.header('Authorization'));
  // 判断请求头中是否包含Authorization字段
  if (!req.header('Authorization')) {
    // 如果没有Authorization字段,返回400状态码,并提示缺少token
    res.status(400).json({ message: '缺少请求头:token为空' });
  } else {
    // 如果有Authorization字段,去掉字段值前面的"Bearer ",提取出token
    const token = req.header('Authorization').replace('Bearer ', '');
    // 打印提取出的token,用于调试查看token的值
    console.log('token:::', token);
    try {
      // 使用jsonwebtoken的verify方法验证token是否有效
      // 第一个参数是token,第二个参数是签名密钥(这里写的是'39qw89r23890',实际项目中应使用环境变量等安全方式存储)
      const decoded = jwt.verify(token, '39qw89r23890');
      // 打印解码后的token内容,用于调试查看解码结果
      console.log("decoded:::", decoded);
      // 将解码后的用户信息存储到req.user中,方便后续的路由处理函数使用
      req.user = decoded;
      // 如果token验证通过,调用next函数,继续执行后续的中间件或路由处理函数
      next();
    } catch (error) {
      // 如果token验证失败(例如token过期、被篡改等),捕获错误
      // 返回400状态码,并提示认证失败
      res.status(400).json({ message: 'Authentication failed' });
    }
  }
};

// 导出auth中间件,供其他模块使用
module.exports = auth;

这段代码实现了一个 JWT (JSON Web Token) 认证中间件,主要用于验证和保护 Node.js 后端 API 路由。以下是详细解析:


核心功能解析
1. 检查请求头中的 Token
console.log(req.header('Authorization')) // 调试输出
if(!req.header('Authorization')){
  res.status(400).json({ message: '缺少请求头:token为空' });
}

作用:检查请求头是否包含 Authorization 字段
失败处理:如果不存在,返回 400 状态码和错误消息
安全提示:实际生产环境建议返回 401 Unauthorized 更符合 HTTP 规范

2. 提取并清理 Token
const token = req.header('Authorization').replace('Bearer ', '');
console.log('token:::',token) // 调试输出

处理逻辑:从 Authorization: Bearer <token> 格式中提取纯 Token
格式要求:前端需按标准格式发送 Token,例如:

GET /api/protected-route
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
3. JWT 验证
const decoded = jwt.verify(token, '39qw89r23890');
console.log("decoded:::",decoded)
req.user = decoded;
next();

验证过程
• 使用密钥 '39qw89r23890' 解密 Token
• 验证 Token 的有效性(是否过期、是否被篡改)
成功处理
• 将解码后的用户数据挂载到 req.user(通常包含用户ID、角色等)
• 调用 next() 进入下一个中间件或路由处理器
密钥注意:生产环境应使用环境变量存储密钥,而非硬编码

4. 错误处理
catch (error) {
  res.status(400).json({ message: 'Authentication failed' });
}

捕获场景
• Token 过期(TokenExpiredError
• Token 被篡改(JsonWebTokenError
• 其他验证错误
改进建议

res.status(401).json({ 
  code: 'INVALID_TOKEN',
  message: 'Token验证失败',
  error: error.message // 可选:开发环境返回具体错误
});

完整工作流程
  1. 前端请求 → 携带 Authorization
  2. 中间件检查 → 无Token → 立即拒绝
  3. Token验证 → 无效 → 返回错误
  4. 验证通过 → 附加用户数据到 req.user → 放行到后续路由

安全注意事项
  1. 密钥管理

    // 推荐从环境变量读取
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
  2. Token 存储
    • 前端应使用 HttpOnly Cookie 或 localStorage 安全存储
    • 避免在 URL 中传递 Token(会记录到日志)

  3. 增强验证

    // 可选:检查Token是否在黑名单(如用户登出后)
    if (await isTokenRevoked(token)) {
      return res.status(401).json({ message: 'Token已失效' });
    }
    
  4. 日志安全
    • 实际生产环境应移除 console.log(token),避免敏感信息泄露


典型使用场景
const express = require('express');
const auth = require('./middleware/auth');

const app = express();
app.get('/protected', auth, (req, res) => {
  // 只有通过认证的请求能进入这里
  res.json({ 
    data: '敏感数据',
    user: req.user // 来自解码的Token信息
  });
});

与其他中间件的协作
中间件执行顺序功能补充
express.json()需在 auth 之前解析请求体供后续使用
helmetauth 之前增强HTTP头安全性
rate-limiterauth 之前防止暴力破解

总结
功能模块关键点
Token检查验证 Authorization 头是否存在
JWT解密使用密钥验证Token有效性
数据传递通过 req.user 共享用户信息
错误处理拦截非法请求并返回标准错误

此中间件是保护API路由的基础安全层,确保只有携带有效Token的请求能访问受保护资源。

3. 然后,在middleware下创建 js 文件 checkPermission.js ,代码如下:

// 引入数据库连接池模块,用于执行数据库查询操作
const pool = require('../db');

// 定义一个名为checkPermission的高阶函数,用于创建权限检查中间件
// requiredPermission参数表示需要检查的权限代码
const checkPermission = (requiredPermission) => {
  // 返回一个异步中间件函数
  return async (req, res, next) => {
    try {
      // 根据req.user.id查询用户信息
      const [userRows] = await pool.query('SELECT * FROM Users WHERE id = ?', [req.user.id]);
      // 如果用户不存在,返回404状态码
      if (userRows.length === 0) {
        return res.status(404).json({ message: 'User not found' });
      }

      // 查询用户所拥有的角色及其对应的权限
      // 通过多表连接查询,获取用户的角色和权限信息
      const [roleRows] = await pool.query(`
        SELECT Roles.id, Roles.name, Permissions.name as permission_name, Permissions.permission_code
        FROM UserRoles
        JOIN Roles ON UserRoles.role_id = Roles.id
        JOIN RolePermissions ON Roles.id = RolePermissions.role_id
        JOIN Permissions ON RolePermissions.permission_id = Permissions.id
        WHERE UserRoles.user_id = ?
      `, [req.user.id]);

      // 提取用户所拥有的权限代码
      const userPermissions = roleRows.map(role => role.permission_code);
      // 打印用户所拥有的权限代码,用于调试
      console.log("userPermissions:::", userPermissions);
      // 将用户权限存入res.locals,方便后续使用(注释掉了)
      // res.locals.userPermissions = userPermissions;

      // 检查用户是否拥有所需的权限
      if (userPermissions.includes(requiredPermission)) {
        // 如果用户拥有权限,调用next函数继续执行后续中间件或路由处理函数
        return next();
      } else {
        // 如果用户没有权限,返回400状态码并提示拒绝访问
        return res.status(400).json({ message: '拒绝访问:当前角色暂未有此权限' });
      }
    } catch (error) {
      // 如果发生错误,返回500状态码并提示内部服务器错误
      return res.status(500).json({ message: 'Internal server error' });
    }
  };
};

// 导出checkPermission函数,供其他模块使用
module.exports = checkPermission;

这段代码实现了一个 动态权限检查中间件,用于在 Node.js 后端中对 API 请求进行细粒度的权限控制。以下是详细解析:


核心功能解析
1. 中间件工厂模式
const checkPermission = (requiredPermission) => {
  return async (req, res, next) => { /*...*/ }
}

作用:接收一个权限码参数(如 'user_delete'),生成对应的权限检查中间件
灵活度:可以针对不同路由要求不同权限(例如:checkPermission('user_read')checkPermission('admin_access')

2. 用户存在性验证
const [userRows] = await pool.query('SELECT * FROM Users WHERE id = ?', [req.user.id]);
if (userRows.length === 0) {
  return res.status(404).json({ message: 'User not found' });
}

前置条件:要求 req.user.id 必须存在(通常由前面的 JWT 中间件设置)
安全设计:即使 Token 有效,也会二次确认用户是否存在

3. 多表联合查询权限
SELECT 
  Roles.id, 
  Roles.name, 
  Permissions.name as permission_name, 
  Permissions.permission_code
FROM UserRoles
JOIN Roles ON UserRoles.role_id = Roles.id
JOIN RolePermissions ON Roles.id = RolePermissions.role_id
JOIN Permissions ON RolePermissions.permission_id = Permissions.id
WHERE UserRoles.user_id = ?

查询逻辑

  1. 通过 UserRoles 表找到用户关联的角色
  2. 通过 RolePermissions 表找到角色关联的权限
  3. 最终获取权限代码(permission_code
    数据结构
// 查询结果示例
[{
  id: 1,
  name: '管理员',
  permission_name: '删除用户',
  permission_code: 'user_delete'
}]
4. 权限校验逻辑
const userPermissions = roleRows.map(role => role.permission_code);
if (userPermissions.includes(requiredPermission)) {
  return next(); // 放行
} else {
  return res.status(400).json({ message: '拒绝访问:当前角色暂未有此权限' });
}

校验方式:检查用户权限列表中是否包含所需权限码
改进建议
• 使用 403 Forbidden 状态码更符合 HTTP 规范(400 表示客户端错误)
• 返回更详细的错误信息:
javascript res.status(403).json({ code: 'PERMISSION_DENIED', required: requiredPermission, owned: userPermissions });

5. 错误处理
catch (error) {
  return res.status(500).json({ message: 'Internal server error' });
}

捕获范围:数据库查询错误、网络问题等
改进建议

console.error('权限检查错误:', error);
res.status(500).json({
  code: 'SERVER_ERROR',
  message: '权限验证服务不可用'
});

完整工作流程
  1. 请求进入 → 携带已认证的用户信息(req.user.id
  2. 数据库查询 → 获取用户关联的所有权限
  3. 权限比对 → 检查是否包含所需权限
  4. 结果处理
    • 通过 → 调用 next() 进入业务逻辑
    • 拒绝 → 返回 403 错误

典型使用场景
const express = require('express');
const checkPermission = require('./middleware/checkPermission');

const app = express();

// 删除用户接口需要 user_delete 权限
app.delete('/users/:id', 
  checkPermission('user_delete'), 
  (req, res) => {
    // 只有有权限的用户能执行到这里
    res.json({ success: true });
  }
);

安全增强建议
  1. 权限缓存

    // 使用Redis缓存用户权限,减少数据库查询
    const cachedPerms = await cache.get(`user:${req.user.id}:perms`);
    if (cachedPerms) {
      return cachedPerms.includes(requiredPermission) ? next() : deny();
    }
    
  2. 权限继承

    // 实现权限继承(如admin自动拥有所有权限)
    if (userRoles.some(role => role.is_admin)) {
      return next();
    }
    
  3. 批量权限检查

    // 扩展支持多权限检查(需满足任意一个或全部)
    checkPermissions({
      any: ['user_read', 'admin_access'],
      all: ['log_export']
    })
    

与其他中间件的协作
执行顺序中间件功能
1express.json()解析请求体
2auth (JWT验证)用户认证
3checkPermission权限控制
4业务逻辑处理实际请求

总结
功能模块关键点优化方向
中间件工厂动态生成权限检查器支持更复杂权限逻辑
数据库查询多表联合查询权限添加缓存机制
权限校验简单包含检查支持权限继承/组合
错误处理基础错误捕获更精细的错误分类

此中间件实现了 RBAC (基于角色的访问控制) 的核心功能,是保护敏感 API 的关键安全层。

4. 最后,在接口配置中增加上面两个中间件

// router/user.js

const express = require("express")
const router = express.Router()

// 引入控制层
const User = require("../controllers/users")

// 引入中间件
const checkPermission = require('../middleware/checkPermission');
const auth = require('../middleware/auth');

// User.create这个方法就是添加用户的,所以及对应的权限就是user_add。
// 因此就把user_add传给中间件checkPermission,让他进行判断当前用户是否有此权限

router.post('/create', auth, checkPermission('user_add'), User.create) 
router.post('/delet', auth, checkPermission('user_delete'), User.delet) 
router.post('/edit', auth, checkPermission('user_edit'), User.edit) 
// 等等....
// 导出
module.exports = route

ter()

// 引入控制层
const User = require(“…/controllers/users”)

// 引入中间件
const checkPermission = require(‘…/middleware/checkPermission’);
const auth = require(‘…/middleware/auth’);

// User.create这个方法就是添加用户的,所以及对应的权限就是user_add。
// 因此就把user_add传给中间件checkPermission,让他进行判断当前用户是否有此权限

router.post(‘/create’, auth, checkPermission(‘user_add’), User.create)
router.post(‘/delet’, auth, checkPermission(‘user_delete’), User.delet)
router.post(‘/edit’, auth, checkPermission(‘user_edit’), User.edit)
// 等等…
// 导出
module.exports = route












相关文章:

  • 搜索网站建设推广优化廊坊优化外包
  • 盐田网站建设怎样建网站卖东西
  • 做网推的网站国内建站平台
  • 网站建设谈单思路百度空间登录
  • 网站的信任度郑志平爱站网创始人
  • 加强网站备案管理专项行动seo公司系统
  • geoserver搭建Docker一键直接安装并上传tif影像预览
  • Java 集合有序性与重复性总结及记忆技巧
  • TreeMap 核心知识点与面试题解析
  • Docker 核心组件
  • 【教学类-102-03】自制剪纸图案(留白边、沿线剪)03——Python制作白色描边和黑点虚线间隔(透明png图片)
  • 【Introduction to Reinforcement Learning】翻译解读4
  • KWDB创作者计划—边缘计算:从概念到落地的技术解读
  • 荣耀90 GT信息
  • 【微机及接口技术】- 第八章 可编程并行接口
  • 软考中级-软件设计师 2023年上半年下午题真题解析:通关秘籍+避坑指南
  • R语言进行聚类分析
  • JBDC java数据库连接(2)
  • System V 信号量:控制进程间共享资源的访问
  • WPS JS宏编程教程(从基础到进阶)-- 第五部分:JS数组与WPS结合应用
  • 4.8学习总结 贪心算法+Stream流
  • 获取cookie的chrome插件:Get cookies.txt LOCALLY
  • 前端知识点---闭包(javascript)
  • 【设计模式】创建型 -- 单例模式 (c++实现)
  • 最小K个数
  • ecovadis认证基本概述,ecovadis认证审核有效期