搭建一个基于角色的权限验证框架

news2024/11/13 10:15:50

说明:基于角色的权限验证(Role-Based Access Control,RBAC)框架,是目前大多数服务端的框架。本文介绍如何快速搭建一个这样的框架,不用Shiro、Spring Security、Sa-Token这样的“大框架”实现。

RBAC

基于角色的权限验证,包含了三个实体,用户、角色、权限,三种关系如下:

在这里插入图片描述

一个用户可以有多个角色,一个角色有多个权限,例如,某用户张三,拥有超级管理员权限、普通用户这两个角色,而其中超级管理员具有删除用户、创建用户权限。

综上分析,三个实体,两个多对多关系,一般来说,我们需要以下五张表:

  • 用户表;

  • 角色表;

  • 权限表;

  • 用户角色表;

  • 角色权限表;

前三张表是一定要有的,后面两张关系表,可以放到前面表里面,作为一个字段存进去,但不建议,难以维护,查询也不方便。

在这里插入图片描述

搭建

分析完了,开始搭建这样一个框架。前面说了,这里不用Shiro、Spring Security、Sa-Token,这里介绍一个GitHub项目:

  • SpringBoot-Shiro-Vue

最初这位大佬应该是想做一个Shiro的Demo,做到最后发现没有Shiro也能实现基于角色的权限验证,就把Shiro依赖去掉了。我是无意中找到的,发现还不错,如果想搭建一个这样的权限验证框架,这个就可以了。简单的,就是好的。

首先,把项目Clone下来,打开,如下:

在这里插入图片描述

介绍

接着来介绍这个项目是如何实现RBAC的,RBAC要解决的是下面几个问题:

  • 当前用户的权限如何存储?

  • 如何实现当前用户对接口级别的权限校验?

  • 权限校验怎么实现?

  • ……

这是我能想到的几个问题?分析一下,

第一个问题是怎么存储用户的权限信息,可以存在数据库里,每次请求访问接口时,去查数据库,拿到这个用户的所有权限,但是这样效率低,访问数据库过于频繁,可以考虑存入到ThreadLocal、Caffine等本地缓存里(能存到Redis里吗?可以思考一下);

第二个问题是如何实现权限校验,可以像Spring Security那样,使用拦截器,在访问接口前先拦截下来,然后获取当前用户的所有接口权限,拿到可访问的接口列表,然后加以判断,看当前要访问的接口地址是否在这里面,不在就返回没有权限;

第三个问题是权限校验怎么实现,像我前面说的那样把用户可访问的所有接口地址拿出来,然后校验,也是一种方法,但最好的方法是用AOP+自定义注解,在Controller层的各个接口上打上注解,限定这个接口隶属哪个角色的哪个权限,然后在AOP里面去拿到当前用户的权限列表,看是否有这个权限。

我们来看下,这个项目是怎么做的。


登录

登录,校验用户名、密码,返回Token的同时,将当前用户信息(包括角色、权限)存入到Caffeine中,

(Controller)

    /**
     * 登录
     */
    @PostMapping("/auth")
    public JSONObject authLogin(@RequestBody JSONObject requestJson) {
        CommonUtil.hasAllRequired(requestJson, "username,password");
        return loginService.authLogin(requestJson);
    }

(Service)

    /**
     * 登录表单提交
     */
    public JSONObject authLogin(JSONObject jsonObject) {
        String username = jsonObject.getString("username");
        String password = jsonObject.getString("password");
        JSONObject info = new JSONObject();
        JSONObject user = loginDao.checkUser(username, password);
        if (user == null) {
            throw new CommonJsonException(ErrorEnum.E_10010);
        }
        String token = tokenService.generateToken(username);
        info.put("token", token);
        return CommonUtil.successJson(info);
    }

(校验SQL,就是单纯的根据用户名、密码查询,看是否有这条记录,非常简易,这个正式环境需要改造一下)

    <select id="checkUser" resultType="com.alibaba.fastjson.JSONObject">
        SELECT u.id userId
        FROM sys_user u
        WHERE u.username = #{username}
          AND u.password = #{password}
          AND u.delete_status = '1'
    </select>

(发Token,同时将当前用户信息存入到本地缓存里,key是当前用户的Token)

    /**
     * 用户登录验证通过后(sso/帐密),生成token,记录用户已登录的状态
     */
    public String generateToken(String username) {
        MDC.put("username", username);
        String token = UUID.randomUUID().toString().replace("-", "").substring(0, 20);
        //设置用户信息缓存
        setCache(token, username);
        return token;
    }

(setCache()方法,查找当前用户信息,存入本地缓存)

    @Autowired
    Cache<String, SessionUserInfo> cacheMap;
    
    /**
     * 将用户的信息以 token==SessionUser存入到本地缓存中
     * @param token
     * @param username
     */
    private void setCache(String token, String username) {
        SessionUserInfo info = getUserInfoByUsername(username);
        log.info("设置用户信息缓存:token={} , username={}, info={}", token, username, info);
        cacheMap.put(token, info);
    }

(getUserInfoByUsername()方法,根据用户名查找用户信息)

    /**
     * 根据用户名查询用户信息,包括角色、权限列表
     * @param username
     * @return
     */
    private SessionUserInfo getUserInfoByUsername(String username) {
        SessionUserInfo userInfo = loginDao.getUserInfo(username);
        if (userInfo.getRoleIds().contains(1)) {
            //管理员,查出全部按钮和权限码
            userInfo.setMenuList(loginDao.getAllMenu());
            userInfo.setPermissionList(loginDao.getAllPermissionCode());
        }
        return userInfo;
    }

(getUserInfo()方法,查找当前用户信息的SQL,可以看到关联到了前面提到的五张表)

    <select id="getUserInfo" resultMap="userInfo">
        SELECT u.id              userId,
               u.username,
               u.nickname,
               ur.role_id        roleId,
               p.menu_code       menuCode,
               p.permission_code permissionCode
        FROM sys_user u
                 LEFT JOIN sys_user_role ur on u.id = ur.user_id
                 LEFT JOIN sys_role r ON r.id = ur.role_id
                 LEFT JOIN sys_role_permission rp ON r.id = rp.role_id
                 LEFT JOIN sys_permission p ON rp.permission_id = p.id AND rp.delete_status = '1'
        WHERE u.username = #{username}
          AND u.delete_status = '1'
    </select>

校验

在看下校验是如何实现的,除了登录、登出等几个接口没有权限,其他用户操作、业务操作的接口上都加了自定义注解,如下:

package com.heeexy.example.controller;

import com.alibaba.fastjson.JSONObject;
import com.heeexy.example.config.annotation.Logical;
import com.heeexy.example.config.annotation.RequiresPermissions;
import com.heeexy.example.service.UserService;
import com.heeexy.example.util.CommonUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * @author: heeexy
 * @description: 用户/角色/权限相关controller
 * @date: 2017/11/2 10:19
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    /**
     * 查询用户列表
     */
    @RequiresPermissions("user:list")
    @GetMapping("/list")
    public JSONObject listUser(HttpServletRequest request) {
        return userService.listUser(CommonUtil.request2Json(request));
    }

    @RequiresPermissions("user:add")
    @PostMapping("/addUser")
    public JSONObject addUser(@RequestBody JSONObject requestJson) {
        CommonUtil.hasAllRequired(requestJson, "username, password, nickname, roleIds");
        return userService.addUser(requestJson);
    }

    @RequiresPermissions("user:update")
    @PostMapping("/updateUser")
    public JSONObject updateUser(@RequestBody JSONObject requestJson) {
        CommonUtil.hasAllRequired(requestJson, " nickname, roleIds, deleteStatus, userId");
        return userService.updateUser(requestJson);
    }

    @RequiresPermissions(value = {"user:add", "user:update"}, logical = Logical.OR)
    @GetMapping("/getAllRoles")
    public JSONObject getAllRoles() {
        return userService.getAllRoles();
    }

    /**
     * 角色列表
     */
    @RequiresPermissions("role:list")
    @GetMapping("/listRole")
    public JSONObject listRole() {
        return userService.listRole();
    }

    /**
     * 查询所有权限, 给角色分配权限时调用
     */
    @RequiresPermissions("role:list")
    @GetMapping("/listAllPermission")
    public JSONObject listAllPermission() {
        return userService.listAllPermission();
    }

    /**
     * 新增角色
     */
    @RequiresPermissions("role:add")
    @PostMapping("/addRole")
    public JSONObject addRole(@RequestBody JSONObject requestJson) {
        CommonUtil.hasAllRequired(requestJson, "roleName,permissions");
        return userService.addRole(requestJson);
    }

    /**
     * 修改角色
     */
    @RequiresPermissions("role:update")
    @PostMapping("/updateRole")
    public JSONObject updateRole(@RequestBody JSONObject requestJson) {
        CommonUtil.hasAllRequired(requestJson, "roleId,roleName,permissions");
        return userService.updateRole(requestJson);
    }

    /**
     * 删除角色
     */
    @RequiresPermissions("role:delete")
    @PostMapping("/deleteRole")
    public JSONObject deleteRole(@RequestBody JSONObject requestJson) {
        CommonUtil.hasAllRequired(requestJson, "roleId");
        return userService.deleteRole(requestJson);
    }
}

如查看用户列表接口上加的注解,如下:

    @RequiresPermissions(value = {"user:add", "user:update"}, logical = Logical.OR)

表示的是访问此接口需要拥有添加用户或者更新用户权限,后面的logical = Logical.OR也是自定义的

然后看AOP里面是怎么实现的,这是RBAC的精华的,如下:

package com.heeexy.example.config.filter;

import com.heeexy.example.config.annotation.Logical;
import com.heeexy.example.config.annotation.RequiresPermissions;
import com.heeexy.example.config.exception.UnauthorizedException;
import com.heeexy.example.dto.session.SessionUserInfo;
import com.heeexy.example.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Set;

/**
 * @author heeexy
 * @description: [角色权限]控制拦截器
 */
@Aspect
@Slf4j
@Component
@Order(3)
public class PermissionAspect {
    @Autowired
    TokenService tokenService;

    @Before("@annotation(com.heeexy.example.config.annotation.RequiresPermissions)")
    public void before(JoinPoint joinPoint) {
        log.debug("开始校验[操作权限]");
        SessionUserInfo userInfo = tokenService.getUserInfo();
        Set<String> myCodes = userInfo.getPermissionList();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        RequiresPermissions a = methodSignature.getMethod().getAnnotation(RequiresPermissions.class);
        String[] perms = a.value();
        log.debug("校验权限code: {}", Arrays.toString(perms));
        log.debug("用户已有权限: {}", myCodes);
        //5.对比[要求]的code和[用户实际拥有]的code
        if (a.logical() == Logical.AND) {
            //必须包含要求的每个权限
            for (String perm : perms) {
                if (!myCodes.contains(perm)) {
                    log.warn("用户缺少权限 code : {}", perm);
                    throw new UnauthorizedException();//抛出[权限不足]的异常
                }
            }
        } else {
            //多个权限只需包含其中一种即可
            boolean flag = false;
            for (String perm : perms) {
                if (myCodes.contains(perm)) {
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                log.warn("用户缺少权限 code= : {} (任意有一种即可)", Arrays.toString(perms));
                throw new UnauthorizedException();//抛出[权限不足]的异常
            }
        }
    }
}

这里的实现也很容易理解,先从本地缓存中取出当前用户的权限列表,然后再取出接口上面的权限列表,和逻辑运算符(AND还是OR),加以判断,看当前用户是否包含了这个接口所需要的权限。

以上,就是这个项目对RBAC的实现,没有用到第三方权限验证框架,短小精悍,很有启发。如果你需要搭建这样一个登录框架,可以看看这个项目,删删减减就可以拿来用了。另外,作者还提供了配套的前端Vue项目,也可以部署看看。

总结

本文介绍了Github作者Heeexy的SpringBoot-Shiro-Vue项目,可以快速搭建一个基于角色的权限验证框架。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2143098.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【图像匹配】基于‌墨西哥帽小波(Marr小波)算法的图像匹配,matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于‌墨西哥帽小波&#xff08;Marr小波&#xff09;算法的图像匹配&#xff0c;用…

09_Python流程控制_分支

流程控制 流程控制是管理程序执行顺序的重要组成部分。分支&#xff08;也称为条件语句&#xff09;是流程控制的一种形式&#xff0c;它允许程序根据某些条件的真假来选择执行不同的代码路径。 顺序结构&#xff1a;按部就班执行选择结构&#xff1a;根据条件不同执行循环结…

项目实训:CSS基本布局理解——WEB开发系列38

对CSS学习已经接近尾声&#xff0c;下面你可以对以下两道“小卡拉米”测试进行测试下CSS理解程度。 题 1&#xff1a;基于栅格布局的现代博客首页设计 题目要求&#xff1a; 创建一个博客首页布局&#xff0c;包含一个顶部导航栏、一个主要的内容区域&#xff08;左侧为博客文…

Netty笔记09-网络协议设计与解析

文章目录 前言一、协议设计1. 数据格式2. 消息长度3. 编码方式4. 错误处理5. 安全性 二、协议解析1. 消息分隔2. 粘包与半包处理3. 校验机制 三、为什么需要协议&#xff1f;四、redis 协议五、HTTP 协议六、自定义协议要素编解码器&#x1f4a1; 什么时候可以加 Sharable 前言…

使用 PHPstudy 建立ThinkPHP8 本地集成环境

安装Composer 下载地址&#xff1a;https://getcomposer.org/Composer-Setup.exehttps://getcomposer.org/Composer-Setup.exe 打开PHPstudy创建网站&#xff1a; cmd终端进入PHPstudy www根目录下&#xff1a; 执行代码&#xff1a;cd phpstudy www 根目录地址 cd C:\phpst…

甲骨文发布全球首个采用英伟达™(NVIDIA®)Blackwell GPU的Zettascale人工智能超级计算集群

甲骨文公司宣布推出全球首个Zettascale云计算集群。 该集群配备了令人印象深刻的 131,072 个英伟达Blackwell GPU&#xff0c;能够提供 2.4 ZettaFLOPS 的峰值性能。 这一强大的人工智能基础设施使企业能够以更大的灵活性和主权处理大规模人工智能工作负载。 Oracle云计算基础…

算法_宽度优先搜索解决FloodFill---持续更新

文章目录 前言什么是FloodFill算法图像渲染题目要求题目解析代码如下 岛屿数量题目要求题目解析代码如下 岛屿的最大面积题目要求题目解析代码如下 被围绕的区域题目要求题目解析代码如下 前言 本文将会向你介绍宽度优先搜索解决FloodFill算法相关题型&#xff1a;图像渲染、岛…

2019-2023(CSP-J)选择题真题解析

1&#xff0c;了解的知识 中国的国家顶级域名是&#xff08; &#xff09;【2019年CSP-J初赛选择题第一题】 A…cn B…ch C…chn D…china 【答案】&#xff1a;A 以下哪个奖项是计算机科学领域的最高奖&#xff1f;&#xff08; &#xff09;【2019年CSP-J初赛选择题第…

2025年最新大数据毕业设计选题-基于Hive分析相关

选题思路 回忆学过的知识(Python、Java、Hadoop、Hive、Sqoop、Spark、算法等等。。。) 结合学过的知识确定大的方向 a. 确定技术方向&#xff0c;比如基于Hadoop、基于Hive、基于Spark 等等。。。 b. 确定业务方向&#xff0c;比如民宿分析、电商行为分析、天气分析等等。。。…

uniapp uview扩展u-picker支持日历期间 年期间 月期间 时分期间组件

uniapp uview扩展u-picker支持日历期间 年期间 月期间 时分期间组件 日历期间、年期间、月期间及时分期间组件在不同的应用场景中发挥着重要的作用。这些组件通常用于表单、应用程序或网站中&#xff0c;以方便用户输入和选择特定的日期和时间范围。以下是这些组件的主要作用&a…

【读书】原则

后面的 太长了&#xff0c;而且太多了 我看作者 49年的 0多岁的老人的谆谆教诲 太多了 一下子吃不消 分为 生活原则 和 工作原则 倡导 人要以 原则而活 要做到极度透明 极度求真和极度透明&#xff1a;在软件开发中&#xff0c;对事实的执着追求和对信息的透明度是至关重要的。…

dedecms——四种webshell姿势

姿势一&#xff1a;通过文件管理器上传WebShell 步骤一&#xff1a;访问目标靶场其思路为 dedecms 后台可以直接上传任意文件&#xff0c;可以通过文件管理器上传php文件获取webshell 步骤二&#xff1a;登陆到后台点击【核心】--》 【文件式管理器】--》 【文件上传】将准备好…

linux系统如何通过进程PID号找到对应的程序在系统中的路径

linux系统如何通过进程PID号找到对应的程序在系统中的路径 首先我们用ps -aux​命令找到对应进程的PID号&#xff0c;比如我这里要得就是xmrig这个进程的PID号 ​​ 通过lsof命令查看对应进程的关联的文件&#xff0c;并找到可执行文件的路径 lsof -p 22785 | grep txt​​ 或…

SpringCloud Feign 以及 一个标准的微服务的制作

一个标准的微服务制作 以一个咖啡小程序项目的订单模块为例&#xff0c;这个模块必将包括&#xff1a; 各种实体类&#xff08;pojo,dto,vo....&#xff09; 控制器 controller 服务类service ...... 其中控制器中有的接口需要提供给其他微服务&#xff0c;订单模块也需要…

软件无线电2:矢量信号器和HackRF实现FM调制解调

前面实现了在matlab平台下的FM收发&#xff0c;那么如果将matlab中的数据应用在真实的无线电台中会是怎样呢&#xff1f;于是我们借助矢量信号器和HackRF实现了射频下的FM调制解调。注意本文仅用于科研和学习&#xff0c;私自搭建电台属于违法行为。 1. 概述 整体实现框图如下…

【梯度下降|链式法则】卷积神经网络中的参数是如何传输和更新的?

【梯度下降|链式法则】卷积神经网络中的参数是如何传输和更新的&#xff1f; 【梯度下降|链式法则】卷积神经网络中的参数是如何传输和更新的&#xff1f; 文章目录 【梯度下降|链式法则】卷积神经网络中的参数是如何传输和更新的&#xff1f;1. 什么是梯度&#xff1f;2.梯度…

【网络安全】分享4个高危业务逻辑漏洞

未经许可,不得转载。 文章目录 正文逻辑漏洞1逻辑漏洞2逻辑漏洞3逻辑漏洞4其它正文 该目标程序是一家提供浏览器服务的公司,其核心功能是网页抓取和多账户登录操作,类似于浏览器中的隐身模式,但更加强大和高效。通过该平台,用户可以轻松管理并同时运行数百个隐身浏览器实…

【Python123题库】#绘制温度曲线 #XRD谱图绘制 #态密度曲线绘制

禁止转载&#xff0c;原文&#xff1a;https://blog.csdn.net/qq_45801887/article/details/140087866 参考教程&#xff1a;B站视频讲解——https://space.bilibili.com/3546616042621301 有帮助麻烦点个赞 ~ ~ Python123题库 绘制温度曲线XRD谱图绘制态密度曲线绘制 绘制温度…

【LeetCode】每日一题 2024_9_16 公交站间的距离(模拟)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;公交站间的距离 代码与解题思路 func distanceBetweenBusStops(distance []int, start int, destination int) int {// 首先让 start > destination, 这两个谁大对结果没有影响&#…

监控易监测对象及指标之:全面监控DB2_linux数据库

在数字化时代&#xff0c;数据库作为企业核心数据资产的存储和管理中心&#xff0c;其稳定性和性能直接关系到业务的连续性和效率。DB2作为IBM推出的关系型数据库管理系统&#xff0c;广泛应用于各种业务场景。为了确保DB2_linux数据库的稳定运行和高效性能&#xff0c;全面而细…