全栈项目权限分级怎么设计?

访客 全栈框架 2

本文目录导读:

  1. 核心基础模型:RBAC(基于角色的访问控制)
  2. 后端设计(以 Node.js + Express 为例)
  3. 前端实现
  4. 更精细化的设计扩展
  5. 关键最佳实践

全栈项目的权限分级设计是一个核心问题,设计得好能让系统安全、易维护,设计得差则可能导致安全漏洞或代码混乱。

下面从基础模型、后端实现、前端控制、数据库设计最佳实践五个层面来拆解。

核心基础模型:RBAC(基于角色的访问控制)

绝大多数全栈项目都采用 RBAC 模型,它的核心思想是:用户 -> 角色 -> 权限

  • 用户 (User):系统的使用者。
  • 角色 (Role):权限的集合,如“管理员”、“编辑者”、“普通用户”。用户不直接拥有权限,而是拥有角色
  • 权限 (Permission):对某个资源的操作能力,如“创建文章”、“删除用户”、“查看报表”。

为什么用 RBAC? 它解决了“给每个用户单独分配权限”的痛点,你只需要管理少数几个角色,然后给用户分配角色即可,极大降低了维护成本。

后端设计(以 Node.js + Express 为例)

后端是权限控制的基石,必须做到“后端不可信”,前端可以隐藏按钮,但后端必须校验。

数据库表结构设计(核心)

通常需要三张核心表,两张中间表:

  • users (用户表): id, username, password, role_id (外键,关联到角色)
  • roles (角色表): id, name (如 admin, editor, user), description
  • permissions (权限表): id, name (如 article:create), description
  • role_permissions (角色-权限关联表): role_id, permission_id
  • user_roles (用户-角色关联表,可选):如果你的用户只有一个角色,可以直接在 users 表里用 role_id,如果用户可以有多个角色,则需要这张关联表。

权限命名规范(重要): 推荐使用 资源:操作资源.操作 的格式,便于统一管理。

  • system:user:list (查看用户列表)
  • system:user:create (创建用户)
  • system:user:update (更新用户)
  • system:user:delete (删除用户)
  • article:list (查看文章列表)
  • article:create (创建文章)
  • article:edit (编辑自己的文章)
  • article:publish (发布文章 - 通常只有管理员有)

后端中间件实现

你需要实现一个 权限校验中间件

// 伪代码示例
// middleware/auth.js
// 1. 验证用户是否登录 (JWT鉴权)
function authenticate(req, res, next) {
    const token = req.headers.authorization?.split(' ')[1];
    try {
        const decoded = jwt.verify(token, SECRET_KEY);
        req.user = decoded; // { userId: 1, role: 'admin' }
        next();
    } catch (err) {
        return res.status(401).json({ error: 'Unauthorized' });
    }
}
// 2. 权限校验中间件 (核心)
function authorize(...requiredPermissions) {
    return (req, res, next) => {
        // 从用户角色中获取所有权限 (建议在登录时缓存到JWT或Redis中)
        const userPermissions = getUserPermissionsFromRole(req.user.role);
        // 或者:const userPermissions = getUserPermissionsFromDatabase(req.user.userId);
        // 检查是否拥有所有必需的权限
        const hasPermission = requiredPermissions.every(permission => 
            userPermissions.includes(permission)
        );
        if (hasPermission) {
            next();
        } else {
            return res.status(403).json({ error: 'Forbidden' });
        }
    };
}
// 3. 在路由中使用
router.get('/api/users', 
    authenticate, 
    authorize('system:user:list'), 
    userController.listUsers
);
router.delete('/api/users/:id', 
    authenticate, 
    authorize('system:user:delete'), 
    userController.deleteUser
);

前端实现

前端的权限控制主要是 用户体验 问题,不能依赖它做安全防护。

获取用户权限

用户登录成功后,后端在返回的JWT中(或另外调用 /api/user/permissions 接口)包含用户的角色权限列表

// 后端返回数据结构
{
    "user": {
        "id": 1,
        "username": "admin",
        "role": "admin"
    },
    "permissions": [
        "system:user:list",
        "system:user:create",
        "system:user:delete",
        "article:create",
        "article:publish"
    ]
}
// 前端可以将其存入全局状态管理 (如 Pinia/Vuex, Redux/Zustand)

组件级别的控制

  • 按钮、菜单的显隐

    // React 示例
    import { usePermissions } from '../hooks/usePermissions';
    function UserListPage() {
        const { hasPermission } = usePermissions();
        return (
            <div>
                <h1>用户管理</h1>
                {hasPermission('system:user:create') && (
                    <button onClick={openCreateModal}>新建用户</button>
                )}
                {/* 删除按钮同理 */}
            </div>
        );
    }
  • 路由级别的控制

    // 实现一个 PrivateRoute 组件或路由守卫
    function PrivateRoute({ children, requiredPermissions }) {
        const { hasPermission } = usePermissions();
        const hasAll = requiredPermissions.every(p => hasPermission(p));
        if (!hasAll) {
            return <Navigate to="/403" replace />; // 或者 <Redirect />
        }
        return children;
    }
    // 路由配置
    <Route path="/admin/users" element={
        <PrivateRoute requiredPermissions={['system:user:list']}>
            <UserListPage />
        </PrivateRoute>
    } />

更精细化的设计扩展

除了基础的 RBAC,实际项目中可能还需要以下扩展:

数据权限(行级权限)

  • 问题:同样是“查看文章”,普通用户只能看自己的文章,管理员能看到所有人的。

  • 解决:在数据库查询时,根据用户的角色和ID拼接不同的 WHERE 条件。

    -- 管理员:查全部
    SELECT * FROM articles;
    -- 普通用户:查自己的
    SELECT * FROM articles WHERE user_id = :currentUserId;
  • 常见模型:按 用户 -> 部门 -> 集团 等组织架构划分数据范围。

字段权限(列级权限)

  • 问题:普通用户查看用户信息时,不能看到“手机号”、“身份证号”。
  • 解决:后端返回数据时,根据权限过滤掉敏感字段。
    // controller 中
    const userData = await User.findById(id);
    if (!req.user.permissions.includes('user:view:phone')) {
        delete userData.phone; // 脱敏
    }

多租户权限(SaaS 场景)

  • 问题:不同公司的用户登录后,只能看到自己公司(租户)内的数据。
  • 解决:在所有涉及到数据的表中都增加一个 tenant_id 字段,数据库查询时必须带上 WHERE tenant_id = :currentTenantId

关键最佳实践

  1. 永远不要相信前端:前端只是装饰,后端必须对每一次请求进行独立的权限校验。
  2. 最小权限原则:给每个角色分配刚刚好的权限,默认不开放任何权限,只开放你需要的。
  3. 权限缓存:不要每次请求都查一次数据库,可以将用户的权限列表缓存在:
    • JWT Token 中(适合权限变动不频繁的场景)。
    • Redis 中(适合权限经常变动的场景,设置过期时间)。
  4. 权限固化:代码中直接使用权限字符串(如 system:user:delete),而不是通过动态配置,权限的增删改应该由代码发布来控制,而不是数据库随意修改。
  5. 避免超管角色:不要设计一个可以绕过所有权限检查的超级管理员角色,如果有超级管理员,它的处理方式应该是:在数据库或配置中预置它的权限列表,并且不要公开它的密码。

一个成熟的全栈项目权限分级设计,可以概括为:

一个模型(RBAC),两层控制(后端强校验 + 前端弱展示),三个维度(功能权限、数据权限、字段权限)。

如果你开发的团队只有几个人、业务逻辑不复杂,可以直接用 用户 -> 角色 -> 权限 的经典 RBAC,随着系统复杂,再逐步引入数据权限和多租户。

标签: 权限粒度

抱歉,评论功能暂时关闭!