10基于访问权限控制和细粒度控制的方式访问资源

访问权限控制

RBAC

基于角色的访问控制(Role-Based Access Control)是按角色进行授权,如主体的角色为总经理时才可以查询企业运营报表和员工工资信息等

  • 缺点:查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为判断用户角色是否为总经理或部门经理,系统可扩展性差
    在这里插入图片描述

基于资源的访问控制(Resource-Based Access Control)是按资源/权限进行授权,把权限打包给角色(角色拥有一组权限)分配给用户(用户拥有多个角色)

  • 优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改授权代码,系统可扩展性强

在这里插入图片描述

权限数据模型

**基于资源的访问控制最少包括用户表、角色表、用户角色表、权限表、角色权限表五张表 **

在这里插入图片描述

用户表: 记录系统中的用户信息

在这里插入图片描述

角色表: 根据系统业务决定系统中所需要的角色
在这里插入图片描述

菜单权限表: 记录系统中操作相关资源的权限

在这里插入图片描述

用户角色表: 指定用户和对应角色的关联关系,一个用户可以有多个角色,一个角色可以被不同用户使用

在这里插入图片描述

角色权限表: 指定角色和权限的关联关系,一个角色可以拥有多个权限,一个权限也可以被多个角色使用

在这里插入图片描述

查询/删除/分配权限

查询用户所拥有的权限: 先根据用户Id查询用户所拥有的角色Id,根据用户的角色Id查询用户所拥有的权限Id,根据权限Id查询具体的权限内容

# 一个用户可以拥有多个角色Id,一个角色Id可以有多个权限
SELECT * FROM xc_menu WHERE id IN(
    SELECT menu_id FROM xc_permission WHERE role_id IN(
        SELECT role_id FROM xc_user_role WHERE user_id = '49'
    )
)

给用户的角色添加权限

  • 首先根据用户的Id查询用户对应的角色,如果没有角色需要先给用户分配角色,有了角色后找到需要分配的权限主键ID,在角色权限关系表中添加一条记录指定用户角色对应的权限
  • 分配完权限后需要重新登录刷新令牌中保存的权限信息

删除用户权限的两种方式

  • 第一种: 给用户换角色(不含删除的权限),此时新角色下的权限就是用户的权限
  • 第二种: 删除角色权限关系表相应记录,此时拥有该角色的用户都将删除此权限

实现用户授权

开发中可以使用图形化的权限管理界面操作数据库完成给用户分配权限、查询用户权限等需求

在这里插入图片描述

环境搭建

nginx.conf中进行配置代理教学机构的管理页面

upstream uidevserver{
    server 127.0.0.1:8601 weight=10;
}
# 前端教学机构管理页面对应虚拟机
server {
    listen       80;
    server_name  teacher.51xuecheng.cn;
    
    // .................
  
    location / {
        proxy_pass   http://uidevserver;
    }

    location /api/ {
        proxy_pass http://gatewayserver/;
    } 
}

实现用户授权

使用Spring Security进行授权,首先在生成jwt令牌前根据用户Id查询用户拥有的权限,然后将查询到的权限写入令牌

在这里插入图片描述

第一步: 在内容管理模块集成Spring Security,在需要授权的接口处使用@PreAuthorize("hasAuthority('权限标识符')")示执行此方法需要授权

  • 如果当前用户请求接口没有权限则抛出异常org.springframework.security.access.AccessDeniedException并提示不允许访问
@RestController
@Api(value = "课程信息编辑接口", tags = "课程信息编辑接口")
public class CourseBaseInfoController {
    @Resource
    CourseBaseInfoService courseBaseInfoService;
	
    
    // 指定访问/course/list接口时需要拥有权限xc_teachmanager_course_list
    @ApiOperation("课程查询接口")
    @PreAuthorize("hasAuthority('xc_teachmanager_course_list')")
    @PostMapping("/course/list")
    public PageResult<CourseBase> list(PageParams pageParams, @RequestBody QueryCourseParamDto queryCourseParams) {
        SecurityUtil.XcUser user = SecurityUtil.getUser();
        Long companyId = null;
        if (StringUtils.isNotEmpty(user.getCompanyId())) {
            companyId = Long.parseLong(user.getCompanyId());
        }
        PageResult<CourseBase> result = courseBaseInfoService.queryCourseBaseList(companyId, pageParams, queryCourseParams);
        return result;
    }
}

第二步: 在base工程下的统一异常处理器GlobalExceptionHandler中处理该异常,为了避免在base工程引入Spring Security依赖(工程也依赖了base工程,引入就会管控工程资源)

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
	
    // 在系统异常中处理AccessDeniedException类型的异常
    @ResponseBody
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public RestErrorResponse exception(Exception e) {
        log.error("【系统异常】{}",e.getMessage(),e);
        e.printStackTrace();
        if(e.getMessage().equals("不允许访问")){
            return new RestErrorResponse("没有操作此功能的权限");
        }
        return new RestErrorResponse(CommonError.UNKOWN_ERROR.getErrMessage());
    }
}

第三步: 编写DAO方法根据用户ID查询用户的权限

public interface XcMenuMapper extends BaseMapper<XcMenu> {
    @Select("SELECT * FROM xc_menu WHERE id IN (
            SELECT menu_id FROM xc_permission WHERE role_id IN (
                SELECT role_id FROM xc_user_role WHERE user_id = #{userId} ))
    ")
    List<XcMenu> selectPermissionByUserId(@Param("userId") String userId);
}

第四步: 修改UserServiceImpl类的getUserPrincipal方法,当认证通过后从数据库中查询用户的权限信息并封装到UserDetails的Username属性中,最后将信息写入到JWT令牌中

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    // 将包含认证参数的Json格式的字符串如username={"username":"yunqing","authType":"password","password":"111111"}`转换为AuthParamsDto对象
    AuthParamsDto authParamsDto = null;
    try {
        authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
    } catch (Exception e) {
        log.error("认证请求数据格式不对:{}", s);
        throw new RuntimeException("认证请求数据格式不对");
    }
    // 获取认证类型,beanName由认证类型和后缀组成,如password_authservice
    String authType = authParamsDto.getAuthType();
    // 根据认证类型从Spring容器中取出对应的bean
    AuthService authService = applicationContext.getBean(authType + "_authservice", AuthService.class);
    // 不同认证方式的认证逻辑不同,但最后都是调用execute方法完成认证
    XcUserExt user = authService.execute(authParamsDto);
    // 认证通过后,查询用户信息并将查询到的信息封装到UserDetails的Username属性中
    return getUserPrincipal(user);
}
public UserDetails getUserPrincipal(XcUserExt user){
    String password = user.getPassword();
    // 默认权限,当用户权限为空时如果不设置权限则报Cannot pass a null GrantedAuthority collection
    String[] authorities=  {"test"};
    // 根据用户id查询用户的所有权限
    List<XcMenu> xcMenus = menuMapper.selectPermissionByUserId(user.getId()); 
    if(xcMenus.size()>0){
        List<String> permissions =new ArrayList<>();
        xcMenus.forEach(m->{
            // 获取用户拥有的权限标识符(菜单编码)并添加到集合里,如xc_teachmanager_course_add表示添加课程权限
            permissions.add(m.getCode());
        });
        //  将permissions转成数组
        authorities = permissions.toArray(new String[0]);
    }    
    // 将用户权限放在XcUserExt中
    user.setPermissions(permissions);
    // 为了安全在令牌中不放密码
    user.setPassword(null);
    // 将user对象转json
    String userString = JSON.toJSONString(user);
    // 设置用户的权限
    String[] authorities = permissions.toArray(new String[0]);
    // 返回UserDetails包含查询到的用户权限信息,最终安全框架会把这些信息写入生成的JWT令牌中
    UserDetails userDetails = User.withUsername(userString).password(password).authorities(authorities).build();
    return userDetails;
}

第五步: 使用教学机构用户登录系统, 点击教学机构首先访问课程查询接口,如果当前登陆用户没有xc_teachmanager_course_list权限报没有此操作的权限错误

在这里插入图片描述

细粒度授权(数据权限)

细粒度授权也叫数据范围授权,即不同的用户虽然所拥有的操作权限相同,但是能够操作的数据范围是不一样的

  • 如用户A和用户B都拥有查询课程权限,但是查询课程时只允许用户查询自己所属的教学机构下的课程信息

细粒度授权涉及到不同的业务逻辑,通常在service层实现,根据不同的用户进行校验,根据不同的参数查询或操作不同的数据

  • 获取当前登录的用户所属教育机构的Id,然后查询该教学机构下的课程信息
@ApiOperation("课程查询接口")
@PreAuthorize("hasAuthority('xc_teachmanager_course_list')")//拥有课程列表查询的权限方可访问
@PostMapping("/course/list")
public PageResult<CourseBase> list(PageParams pageParams, @RequestBody QueryCourseParamsDto queryCourseParams){
    // 使用工具类取出用户身份
    XcUser user = SecurityUtil.getUser();
    // 获取用户所属教育机构的Id
    String companyId = user.getCompanyId();
    return courseBaseInfoService.queryCourseBaseList(Long.parseLong(companyId),pageParams,queryCourseParams);
}
@Slf4j
public class SecurityUtil {
    public static XcUser getUser() {
        try {
            // 通过SecurityContextHolder获取user_name属性的值即包含用户信息的Json字符串
            Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            if (principal instanceof String) {
                // 将包含用户信息的Json字符串转换为XcUser对象
                String userJson = principal.toString();
                XcUser xcUser = JSON.parseObject(userJson, XcUser.class);
                return xcUser;
            }
        } catch (Exception e) {
            log.error("获取当前登录用户身份信息出错:{}", e.getMessage());
            e.printStackTrace();
        }
        return null;
    }
    // 这里使用内部类是为了不让content工程去依赖auth工程
    @Data
    public static class XcUser implements Serializable {
        private static final long serialVersionUID = 1L;
        private String id;
        private String username;
        private String password;
        private String salt;
        private String name;
        private String nickname;
        private String wxUnionid;
        private String companyId;
        /**
        * 头像
        */
        private String userpic;
        private String utype;
        private LocalDateTime birthday;
        private String sex;
        private String email;
        private String cellphone;
        private String qq;
        /**
        * 用户状态
        */
        private String status;
        private LocalDateTime createTime;
        private LocalDateTime updateTime;
    }
}
@Override
@Transactional
public PageResult<CourseBase> queryCourseBaseList(Long companyId, PageParams pageParams, QueryCourseParamDto queryCourseParams) {
    // 构建条件查询器
    LambdaQueryWrapper<CourseBase> queryWrapper = new LambdaQueryWrapper<>();
    // 构建查询条件,按照机构ID查询
    queryWrapper.eq(CourseBase::getCompanyId, companyId);
    // 构建查询条件:按照课程名称模糊查询
    queryWrapper.like(StringUtils.isNotEmpty(queryCourseParams.getCourseName()), CourseBase::getName, queryCourseParams.getCourseName());
    // 构建查询条件,按照课程审核状态查询
    queryWrapper.eq(StringUtils.isNotEmpty(queryCourseParams.getAuditStatus()), CourseBase::getAuditStatus, queryCourseParams.getAuditStatus());
    // 构建查询条件,按照课程发布状态查询
    queryWrapper.eq(StringUtils.isNotEmpty(queryCourseParams.getPublishStatus()), CourseBase::getStatus, queryCourseParams.getPublishStatus());
    // 分页对象
    Page<CourseBase> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
    // 查询数据内容获得结果
    Page<CourseBase> pageInfo = courseBaseMapper.selectPage(page, queryWrapper);
    // 获取数据列表
    List<CourseBase> items = pageInfo.getRecords();
    // 获取数据总条数
    long counts = pageInfo.getTotal();
    // 构建结果集
    return new PageResult<>(items, counts, pageParams.getPageNo(), pageParams.getPageSize());
}        

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Django开发】0到1美多商城项目md教程第3篇:用户注册业务实现,1. 用户注册页面绑定Vue数据【附代码文档】

美多商城完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;欢迎来到美多商城&#xff01;&#xff0c;项目准备。展示用户注册页面&#xff0c;创建用户模块子应用。用户注册业务实现&#xff0c;用户注册前端逻辑。图形验证码&#xff0c;图形验证码接口设…

vue 隐藏导航栏和菜单栏,已解决

初始效果&#xff1a; 效果&#xff1a; 出现问题&#xff1a; 解决方法&#xff1a;

【数字图像处理matlab系列】使用数组索引进行简单的图像裁剪、二次取样操作

【数字图像处理matlab系列】使用数组索引进行简单的图像裁剪、二次取样操作 【先赞后看养成习惯】求点赞+关注+收藏! pout.tif是一张matlab自带的图片,图像尺寸是291*240,使用imread读取该图像>> a = imread(pout.tif); >> imshow(a);对图像a进行上下翻转操作,…

【浅尝C++】类和对象第一弹=>类的定义/访问限定符/实例化/类对象大小计算/this指针

&#x1f3e0;专栏介绍&#xff1a;浅尝C专栏是用于记录C语法基础、STL及内存剖析等。 &#x1f6a9;一些备注&#xff1a;之前的文章有点杂乱&#xff0c;这里将前面的知识点重新组织了&#xff0c;避免了过多冗余的废话。 &#x1f3af;每日努力一点点&#xff0c;技术变化看…

小程序英文口语发音评测

一、英文口语评测需求 在全球化的今天&#xff0c;英语已经成为了世界上最重要的国际语言之一。无论是在国际商务、科技研究、教育还是日常生活中&#xff0c;英语都扮演着举足轻重的角色。因此&#xff0c;掌握英文口语的能力对于个人的职业发展、学术研究以及跨文化交流都具…

【C语言】指针基础知识(二)

一&#xff0c;指针变量类型的意义 1&#xff0c;指针的类型决定了&#xff0c;对指针解引⽤的时候有多⼤的权限&#xff08;⼀次能操作⼏个字节&#xff09;。 例如&#xff1a;char* 的指针解引⽤访问⼀个字节&#xff0c;int* 的指针解引⽤访问四个字节&#xff0c;short*…

DolphinScheduler运维-页面加载缓慢

一、问题描述 DolphinScheduler调度平台的UI界面加载缓慢,项目中的任务实例加载时间过长,需要解决这个问题,提高DolphinScheduler平台UI页面的加载速度。 二、原因分析 经过分析发现,任务实例过多是导致UI加载缓慢的主要原因。由于任务实例无法直接删除,根据文档了解到需…

集成学习 | 集成学习思想:Boosting

目录 一. Boosting思想1. Adaboost 算法1.1 Adaboost算法构建流程1.2 sklearn库参数说明 2. Gradient Boosting 算法2.1 Gradient Boosting算法构建流程2.2 Gradient Boosting算法的回归与分类问题2.2.1 Gradient Boosting回归算法均方差损失函数绝对误差损失函数 2.2.2 Gradie…

SpringMVC结合设计模式:解决MyBatisPlus传递嵌套JSON数据的难题

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &a…

有道翻译实现接口加密解密

文章目录 目标简单逆向分析源码深度逆向分析参考文献目标 实现对网易有道 sign 等参数的加密 及 返回的密文数据解密实现 简单逆向分析 首先在右上角提前登录好账号信息。 输入中文:你好 要求翻译成:英文 全局搜索:你好 或 hello,结果没有发现什么。 切换 Fetch/XHR …

QML ShapePath绘制虚线

一.qml PathLine介绍 在 QML&#xff08;Qt Modeling Language&#xff09;中&#xff0c;PathLine 是 Path 元素的一个子类型&#xff0c;用于创建两点之间的直线段。Path 类型用于描述一个二维路径&#xff0c;可以用来绘制形状、曲线和直线。PathLine 是所有路径曲线中最简单…

Day60:WEB攻防-PHP反序列化POP链构造魔术方法流程漏洞触发条件属性修改

目录 PHP-DEMO1-序列化和反序列化 序列化操作 - 即类型转换 序列化案例 PHP-DEMO2-魔术方法触发规则 __construct(): //当对象new的时候会自动调用 __destruct()&#xff1a;//当对象被销毁时会被自动调用 __sleep(): //serialize()执行时被自动调用 __wakeup(): //uns…

高中信息技术教资刷题笔记_选择题篇

1.信息技术基础 位与字节的换算 模2除法运算 网页保存 进制之间的计算 教你快速学会二进制、十进制、十六进制之间的转换 - 知乎 (zhihu.com) 原码、补码、反码计算 物联网技术 位运算 按位与&#xff1a;同位置为1&#xff0c;则为1&#xff0c;其他都是0按位或&#xff1a;有…

2024年产品品牌化深度分析:消费者心理与品牌化、产品质量的权衡

随着市场竞争的加剧和消费者需求的多样化&#xff0c;产品品牌化已经成为企业不可或缺的战略选择。在2024年&#xff0c;当消费者面对众多商品时&#xff0c;品牌化与产品质量之间的权衡成为了消费者决策的重要因素。那么&#xff0c;在消费者心理中&#xff0c;品牌化重要还是…

Docker 之 数据卷

目录 1. 数据卷是什么 1.1 运行一个带有容器卷存储功能的容器实例 2.能干什么 3. 容器卷案例 3.1 宿主机vs容器之间映射添加容器卷 3.1.1 命令添加&#xff1a; 3.1.2 查看数据卷是否挂载成功 3.1.3 容器和宿主机之间数据共享 3.2 读写规则映射添加说明 3.2.1 读写&…

详解:JS异步解决方案之回调函数,及其弊端

「异步编程」是前端工程师日常开发中经常会用到的技术&#xff0c;异步的实现有好几种方式&#xff0c;各有利弊&#xff0c;本篇先讲通过回调来实现来异步 。 一、同步和异步 同步编程和异步编程是两种不同的编程方式。 同步编程是指按照代码的顺序执行&#xff0c;每一行代…

前端小卡片:vue3路由是什么,有什么作用,该如何配置?

在 Vue 3 中&#xff0c;路由的处理使用了 Vue Router&#xff0c;它是官方提供的路由管理器。Vue Router 用于实现单页应用中的路由功能&#xff0c;通过将不同的 URL 映射到对应的组件&#xff0c;实现页面之间的切换和导航。 Vue Router 的作用包括&#xff1a; 实现页面之…

Python并发编程:线程和多线程的使用

前面的文章&#xff0c;我们讲了什么Python的许多基础知识&#xff0c;现在我们开始对Python并发编程进行学习。我们将探讨 Python 中线程和多线程的使用。帮助大家更好地理解如何使用这种技术。 目录 1. 线程&#xff08;Threads&#xff09; 1.1 Python 中的线程工作原理 …

《妈妈是什么》笔记(五) 一切负面经验都必须转化为正面角度

经典摘录 我的引导原则是&#xff0c;一切负面经验都必须转化为正面角度。我们不能选择孩子的经历&#xff0c;但是可以帮助孩子选择如何看待这些事情&#xff0c;以及如何积极地利用这些事情&#xff0c;锤炼自己的社会交往能力。 比如&#xff0c; 别人&#xff08;老师、同…

正则表达式具体用法大全~持续更新

# 正则表达式&#xff1a; ## 单字符匹配&#xff1a; python # 匹配某个字符串&#xff1a; # text "abc" # ret re.match(b,text) # print(ret.group()) # 点&#xff08;.&#xff09;&#xff1a;匹配任意的字符(除了\n)&#xff1a; # text "\nabc&quo…
最新文章