• No6-5.从零搭建spring-cloud-alibaba微服务框架,添加用户鉴权逻辑,操作权限等(五,no6-5)


      代码地址与接口看总目录:【学习笔记】记录冷冷-pig项目的学习过程,大概包括Authorization Server、springcloud、Mybatis Plus~~~_清晨敲代码的博客-CSDN博客

    之前只零碎的学习过spring-cloud-alibaba,并没有全面了解过,这次学习pig框架时,想着可以根据这个项目学习一下,练练手,于是断断续续的用了几天时间搭建了一下基础框架。目前就先重点记录一下遇到的问题吧,毕竟流程也不是特别复杂,就是有的东西没遇到过了解的也不深~

    由于微服务包括认证这里内容太多,所以分了好几篇~

    第一篇文章:No6.从零搭建spring-cloud-alibaba微服务框架,实现fegin、gateway、springevent等(一)_清晨敲代码的博客-CSDN博客

    文章包括:

    1.将服务系统注册到nacos注册中心;

    2.通过nacos实现配置动态更新;

    3.添加fegin服务,实现服务之间调用;

    4.添加网关(学会使用webflux,学会添加过滤器);

    5.添加log服务,通过springevent实现,并使用注解使用(使用AOP);

    第二篇文章:

    No6.从零搭建spring-cloud-alibaba微服务框架,实现数据库调用、用户认证与授权等(二,no6-2)_清晨敲代码的博客-CSDN博客

    文章包括:

    6.添加 mysql 数据库调用,并使用mybatis-plus操作;

    7.在认证模块添加用户认证,基于oauth2的自定义密码模式(已认证用户是基于自定义token加redis持久化,不是session);

    第三篇文章:

    No6-3.从零搭建spring-cloud-alibaba微服务框架,实现资源端用户认证与授权等(三,no6-3)

    8.在资源端模块添加用户认证,基于授权端的认证token添加逻辑(但是没有处理微服务间的不鉴权调用,微服务间的调用接口都是白名单呢!);

    第三篇文章:

    No6-4.从零搭建spring-cloud-alibaba微服务框架,解决微服务间的不鉴权调用等(四,no6-4)

    9.解决微服务间的不鉴权调用(可修改外部访问鉴权逻辑~)

    本篇内容包括:

    10.添加用户鉴权逻辑,操作权限(基于RBAC逻辑,使用springsecuiry安全注解实现)

    剩余包括(会有变动):

    11.添加用户鉴权逻辑,数据权限(基于RBAC逻辑,使用springsecuiry安全注解实现)

    目录

    A10.添加用户鉴权逻辑(基于RBAC逻辑,使用springsecuiry安全注解实现)

    具体业务(只关注重点难点的,增删改查看pig项目就好,就不详说了代码都能懂):

    数据结构:

    代码实现步骤【这里就不贴代码了,太多了,可以回到顶部看gitee里对应分支的代码】:

    测试:

    遇到的问题:

    1.FIND_IN_SET(str,strlist)

     2.cn.hutool.core.lang.tree.TreeUtil 

    3.mps 的 分页查询 IPage

    4.mps的collection的两种方式

    5.@RestControllerAdvice拦截不到抛出的异常!


    A10.添加用户鉴权逻辑(基于RBAC逻辑,使用springsecuiry安全注解实现)

    注意:由于pig项目开源版没有提供数据权限,所以A10中只有操作权限逻辑,在后面会添加数据权限的

    RBAC(Role-Based Access Control)的逻辑就不重复了,可以看这篇文章:什么是 RBAC 模型?

    总的来说就是基于角色的访问控制。通过用户关联角色,角色关联权限,来间接的为用户赋予权限。

    我们现在有用户信息了,只需要添加上角色信息,权限信息即可,在pig项目中权限信息的操作权限由目录、菜单、按钮的操作来代替;数据权限是由部门来代替的。

    我们开发的逻辑就需要提前理清楚,首先先要知道具体的业务是什么,然后要根据业务整理数据结构是什么,然后再将业务和数据通过代码体现出来。

    具体业务(只关注重点难点的,增删改查看pig项目就好,就不详说了代码都能懂):

    1.菜单信息增删改查;

    2.部门信息增删改查(维护组织架构所需要的,同时也是用户数据权限所需要的);

    3.角色信息增删改查;其中会包括获取菜单列表(里面就包含目录、菜单、按钮的操作);获取部门列表

    4.用户信息增删改查;其中会包括获取角色列表、部门列表;登录时还会根据角色获取权限列表;

    5.最后查询列表时,还要根据数据权限;(A10.先实现前五个) 

    数据结构:

    代码实现步骤【这里就不贴代码了,太多了,可以回到顶部看gitee里对应分支的代码】:

    1.先创建数据库表;

    2.添加与数据表对应的实体类;

    * 在此时整理接口文档,把接口、入参、出参整理完毕;

    3.添加菜单和部门的基础增删改查;期间添加获取菜单下拉列表和部门下拉列表的逻辑;

    4.添加角色的增删改查,同时需要添加角色-菜单表的业务类和mapper接口;期间添加获取角色列表的逻辑;

    5.修改用户VO、DTO类,原来并没有添加权限的相关内容;

    6.添加用户-角色表的操作逻辑,修改用户的增删改查,添加用户的角色部门权限等内容;

    7.添加登录成功后获取用户菜单列表的方法;

    * 在此时测试接口是否正常使用

    8.添加 security 注解 @PreAuthorize 需要的鉴权的类;

    9.如果@PreAuthorize 中鉴权失败接口就调用失败,会抛出 AccessDeniedException 异常,后端要返回清晰地说明,那么我们就添加全局异常处理~

    1. -- 1.就使用 pig 项目数据库中的五张表就行,看下面表明;注意,pig 表的id都不是自增,并且在实体代码中写的是用 mybatis-plus的IdType.ASSIGN_ID生成的,可以我看数据库中的id格式并不是按照这个,所以就修改为使用数据库自增策略(查不到MybatisPlus 主键策略使用场景,后续再了解详情吧)
    2. CREATE TABLE `sys_menu` (
    3. `menu_id` bigint(20) NOT NULL AUTO_INCREMENT,
    4. `name` varchar(32) NOT NULL COMMENT '菜单名称',
    5. `permission` varchar(32) DEFAULT NULL COMMENT '菜单权限标识,用于后端接口鉴权标识',
    6. `path` varchar(128) DEFAULT NULL COMMENT '前端URL,用于vue前端页面路径',
    7. `parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID',
    8. `icon` varchar(32) DEFAULT NULL COMMENT '图标,用于前端菜单、按钮显示的图标',
    9. `sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序值',
    10. `keep_alive` char(1) DEFAULT '0' COMMENT '用于前端页面是否开启缓存,0-开启,1- 关闭',
    11. `type` char(1) DEFAULT NULL COMMENT '菜单类型 (0菜单 1按钮)',
    12. `del_flag` char(1) DEFAULT '0' COMMENT '逻辑删除标记(0--正常 1--删除)',
    13. `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
    14. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    15. `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',
    16. `update_time` datetime DEFAULT NULL COMMENT '更新时间',
    17. PRIMARY KEY (`menu_id`)
    18. ) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='菜单权限表';
    19. CREATE TABLE `sys_dept` (
    20. `dept_id` bigint(20) NOT NULL AUTO_INCREMENT,
    21. `parent_id` bigint(20) DEFAULT NULL COMMENT '父级id',
    22. `name` varchar(50) DEFAULT NULL COMMENT '部门名称',
    23. `ancestors` varchar(100) DEFAULT NULL COMMENT '所有祖级id包括自己,逗号隔开',
    24. `sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
    25. `del_flag` char(1) DEFAULT '0' COMMENT '是否删除 1:已删除 0:正常',
    26. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    27. `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
    28. `update_time` datetime DEFAULT NULL COMMENT '修改时间',
    29. `update_by` varchar(64) DEFAULT NULL COMMENT '更新人',
    30. PRIMARY KEY (`dept_id`)
    31. ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='部门管理';
    32. CREATE TABLE `sys_role` (
    33. `role_id` bigint(20) NOT NULL AUTO_INCREMENT,
    34. `role_name` varchar(64) NOT NULL,
    35. `role_code` varchar(64) NOT NULL COMMENT '角色标识,用于接口判断角色权限的',
    36. `role_desc` varchar(255) DEFAULT NULL COMMENT '角色描述',
    37. `del_flag` char(1) DEFAULT '0' COMMENT '删除标识(0-正常,1-删除)',
    38. `create_time` datetime DEFAULT NULL COMMENT '创建时间',
    39. `update_time` datetime DEFAULT NULL COMMENT '修改时间',
    40. `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',
    41. `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
    42. PRIMARY KEY (`role_id`),
    43. UNIQUE KEY `role_idx1_role_code` (`role_code`)
    44. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统角色表';
    45. CREATE TABLE `sys_role_menu` (
    46. `role_id` bigint(20) NOT NULL,
    47. `menu_id` bigint(20) NOT NULL,
    48. PRIMARY KEY (`role_id`,`menu_id`)
    49. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='角色菜单表';
    50. CREATE TABLE `sys_user_role` (
    51. `user_id` bigint(20) NOT NULL,
    52. `role_id` bigint(20) NOT NULL,
    53. PRIMARY KEY (`user_id`,`role_id`)
    54. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户角色表';
    55. -- 这里缺少一个 sys_role_dept 表,pig 里面是没有的,我们后续追加~
    1. //2.根据那五张表添加实体类,SysDept、SysMenu、SysRole 继承自定义的实体基类 BaseEntity,SysRoleMenu、SysUserRole继承 mybatisplus 的 Model 类
    2. //其中 id 要添加 @TableId(value = "menu_id", type = IdType.AUTO) 注解,并且id要数据库自动生成的
    3. //其中 delFlag 删除标识,要添加 @TableLogic(value = "1", delval = "0") 注解,这样能使用 mps 的逻辑删除和逻辑查询,默认值和删除值也可以通过配置设置。【该注解只对自动注入的 sql 起效,自己在mapper.xml中写的sql不生效】
    4. //拿 SysDept 举例
    5. @Data
    6. @EqualsAndHashCode(callSuper = true)
    7. public class SysDept extends BaseEntity {
    8. private static final long serialVersionUID = 1L;
    9. @TableId(value = "dept_id", type = IdType.AUTO)
    10. private Long deptId;
    11. /**
    12. * 部门名称
    13. */
    14. private String deptName;
    15. /**
    16. * 排序
    17. */
    18. private Integer sortOrder;
    19. /**
    20. * 父级部门id
    21. */
    22. private Long parentId;
    23. /**
    24. * 所有祖级id包括自己,逗号隔开
    25. */
    26. private String ancestors;
    27. /**
    28. * 是否删除 -1:已删除 0:正常
    29. */
    30. @TableLogic
    31. private String delFlag;
    32. }
    1. //3.添加菜单和部门的基础增删改查;
    2. //(1)这里的 service 类中的增删改查使用 mps 自带的,也就是 ServiceImpl 自带的 save()/update()等方法;为了方便记忆,我们自定义的方法名都要带有实体类名字,那么只要方法名中没有带有实体类名就是 mps 提供的【以后用多了就记住了】
    3. //(2)按条件查询时使用 Wrappers 方式
    4. //(3)如果有需要树形结构,使用 hutool 的 TreeUtil 方式,这个很方便,而且可以自由追加属性~
    1. //4.添加角色的增删改查,同时需要添加角色-菜单表的业务类和mapper接口;
    2. //(1)角色里面包含了操作权限和数据权限和角色基本内容,由于角色的操作权限和数据权限关乎用户的权限,而角色基本内容是不会的,所以就将这两个操作分开,并且当修改权限的时候,清除已缓存的认证用户信息和用户的菜单权限【未开启缓存可以不用清除】;修改角色基本信息的时候不用清除;而这就需要用到操作角色-部门、角色-菜单关联表操作【这里先不操作角色-部门,留到后面数据权限时操作】;
    3. //(2)所以对于角色业务来说,增改查业务都是操作角色表,角色权限业务是操作角色-菜单表,而删除角色是操作这两张表
    4. //(3)由于角色的基础增改查都是单表,所以不用重写 service 方法,直接在controller中使用 mps 就行;删除和保存角色的权限时,就需要涉及到两个表操作了,记得加上 @Transactional 注解,防止 sql 语句报错能进行回滚【部门和菜单的其实也可以和角色一样的~】
    1. //5.修改用户VO、DTO类,原来并没有添加权限的相关内容;
    2. //这里原来按照 pig 项目设计时,除用户实体类外,还有两个dto和两个vo类,但是使用逻辑有些乱,所以我又重新梳理了一下,
    3. //除实体类外,我们还需要前端传值用于增改等的UserDTO(包含用户基本和权限、角色),还需要用户业务查询的UserVO(包含用户基本的和角色),还需要用户认证信息的UserInfoVO(包含用户基本和权限、角色);
    1. //6.添加用户-角色表的操作逻辑,修改用户的增删改查,添加用户的角色部门权限等内容;
    2. //(1)增删改查都要涉及到用户-角色表的关联操作;例如添加用户时,需要同时保存关联的用户-角色表,这里就需要给业务方法添加 @Transactional 注解,当出现异常是可以回滚~
    3. //(2)其中分页查询需要查询出角色信息,这就涉及到表关联查询;可以使用 mps 的 collection 关联查询
    4. //(3)由于重改了一下用户信息的dto/vo表,记得将所有涉及到的都修改!!!
    5. //(4)用户登录成功回哪到token,然后根据token获取自己的用户及权限,且认证用户的权限permissions只查询按钮的权限;而菜单和目录的权限是单独查询的;
    6. //(5)还需要在PigUserDetailsService里面设置上角色和权限
    7. 这里会用到 SysUserMapper.xml SysRoleMapper.xml SysMenuMapper.xml ~
    1. //7.添加登录成功后获取用户菜单列表的方法;
    2. //(1)当前菜单分为顶部菜单和左侧菜单,可以理解为顶部为一级,左侧为二级,然后后以此类推。这样的话,用户认证成功后,获取菜单权限可以分好几种:1.返回全部权限,就是根据角色搜索到用户所有的权限;2.返回默认的一个顶部的二级左侧权限,和顶部所有的一级列表。
    3. //第一种通过一个接口入参parentid,仅查询当前用户权限祖级包含parentid就可以,只过滤掉按钮类型;第二种是在第一种基础上过滤掉按钮和顶级菜单,并追加了一个返回顶部一级菜单列表的接口;
    4. //我们这例只实现第一种的。
    1. //8.添加 security 注解 @PreAuthorize 需要的鉴权的类;
    2. //(1)我们使用 security 提供的基于注解鉴权的方式,首先要开启注解鉴权,先前在 @EnablePigResourceServer 注解里面已经设置为开启了:@EnableGlobalMethodSecurity(prePostEnabled = true);
    3. //(2)然后在需要鉴权的方法上面加上 @PreAuthorize("@pms.hasPermission('sys_user_add')") 注解,其中 'sys_user_add' 填写能访问该方法的权限唯一标识集合【权限标识也就是菜单表里面】;
    4. //(3)创建鉴权类,逻辑就是拿到可访问该方法的标识集合,然后判断当前用户是否有这些标识,如果有则直接通过;如果没有则会抛出异常 AccessDeniedException。记得将该类注入到容器中

    测试:

    写到这里,就可以测试一下了,先创建一个账号,然后分角色和权限,然后分别访问有权限的接口和没权限的接口,有权限的接口就可以正常访问,没权限的接口会返回 403 。

    最后,可以看到控制台已经抛出了 AccessDeniedException 异常,如果我们不想显示异常,可以添加全局异常处理类~【这里先简单添加】

    1. //9.如果@PreAuthorize 中鉴权失败接口就调用失败,会抛出 AccessDeniedException 异常,后端要返回清晰地说明,那么我们就添加全局异常处理~
    2. //(1)创建一个类 GlobalExceptionHandle ,添加 @RestControllerAdvice 注解,然后创建方法,入参是要捕获的异常类,并给方法加上 @ExceptionHandler(AccessDeniedException.class) 注解,参数是要捕获的异常类;
    3. //举个例子:
    4. @Slf4j
    5. @Order(Ordered.HIGHEST_PRECEDENCE)
    6. @RestControllerAdvice
    7. public class GlobalExceptionHandle {
    8. public GlobalExceptionHandle(){
    9. System.out.println();
    10. }
    11. /**
    12. * @Description: AccessDeniedException
    13. * @param e
    14. * @Return: com.pig4cloud.pig.common.core.util.R
    15. */
    16. @ExceptionHandler(AccessDeniedException.class)
    17. @ResponseStatus(HttpStatus.FORBIDDEN)
    18. public R handleAccessDeniedException1(AccessDeniedException e) {
    19. String msg = "AbstractAccessDecisionManager.accessDenied"+ e.getMessage();
    20. log.warn("拒绝授权异常信息 ex={}", msg);
    21. return R.failed(e.getLocalizedMessage());
    22. }
    23. }

    然后再次访问未授权的方法,就可以看到拦截并返回的数据啦

    并且还没有报错信息

    遇到的问题:

    1.FIND_IN_SET(str,strlist)

    FIND_IN_SET(str,strlist),该函数的作用是查询字段(strlist) 中是否包含(str)的结果,返回结果为 null或记录 。

    str 要查询的字符串,注意 str 前后不用加逗号~

    strlist 需查询的字段,参数以”,”分隔,形式如 (1,2,6,8,10,22)

     2.cn.hutool.core.lang.tree.TreeUtil 

    作用:返回树形结构类型,默认有 id、parentId、name、weight,可以追加属性,最终会生成如下的格式:

    注意:使用TreeUtil.build(collect, parentid)时,只会拿到从parentid开始的列表!!!

    1. {
    2. //这四个是我们提供给 TreeNode 的属性的
    3. "id": "8",
    4. "parentId": "0",
    5. "weight": 62,
    6. "name": "法整活场",
    7. //这两个是扩展属性,放到 map 里面的
    8. "ancestors": "0,",
    9. "createTime": "2022-11-01 07:44:18",
    10. //如果有子级,就一定会有的属性
    11. "children": [
    12. {
    13. "id": "9",
    14. "parentId": "8",
    15. "weight": 33,
    16. "name": "只111增属证",
    17. "ancestors": "0,8,",
    18. "createTime": "2022-11-01 07:45:32"
    19. }
    20. ]
    21. }

    3.mps 的 分页查询 IPage

    前端传分页数据:当前页码,多少条,排序方式等信息,然后赋值给 Page 对象,之后调用并将page传值给 mps 自带的service、mapper的 page()方法,最终的结构就是:

    1. {
    2. "records": [
    3. {...}
    4. ],
    5. "total": 5,
    6. "size": 3,
    7. "current": 1,
    8. "orders": [],
    9. "optimizeCountSql": true,
    10. "searchCount": true,
    11. "countId": null,
    12. "maxLimit": null,
    13. "pages": 2
    14. }

    4.mps的collection的两种方式

    一种是一个 sql 查询,但是多表时查询速度较慢;见SysUserMapper.xml里面的resultMap id="userVoResultMap"

    一种是多个sql查询出需要的数据。主要用于关联多表查询,提升查询速度;见SysUserMapper.xml里面的resultMap id="baseResultMap";

    5.@RestControllerAdvice拦截不到抛出的异常!

    前提是:该全局异常已添加到容器中

    一切都准备好了但就是捕获不到 AccessDeniedException 异常!!!!

    最后发现 @ExceptionHandler(AccessDeniedException.class) 里面的AccessDeniedException异常 import 的是 java.nio.file.AccessDeniedException 类的,我裂开了o(▼皿▼メ;)o

  • 相关阅读:
    eclipse启动tomcat无法访问
    深度学习——(9)神经网络参数详解
    【数据结构】【版本2.0】【树形深渊】——二叉树入侵
    京东云开发者|经典同态加密算法Paillier解读 - 原理、实现和应用
    [全家桶之SpringMVC]SpringMVC 的特点和优势
    asp.net core webapi接收application/x-www-form-urlencoded和form-data参数
    设计模式-适配器模式
    桌面、文档、下载等文件夹移动后无法复原或desktop.ini不起作用的修复方法
    手机APP测试流程规范和方法你知道多少?
    【云原生】使用Docker快速部署openGauss主备环境
  • 原文地址:https://blog.csdn.net/vaevaevae233/article/details/127614471