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.就使用 pig 项目数据库中的五张表就行,看下面表明;注意,pig 表的id都不是自增,并且在实体代码中写的是用 mybatis-plus的IdType.ASSIGN_ID生成的,可以我看数据库中的id格式并不是按照这个,所以就修改为使用数据库自增策略(查不到MybatisPlus 主键策略使用场景,后续再了解详情吧)

CREATE TABLE `sys_menu` (
  `menu_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL COMMENT '菜单名称',
  `permission` varchar(32) DEFAULT NULL COMMENT '菜单权限标识,用于后端接口鉴权标识',
  `path` varchar(128) DEFAULT NULL COMMENT '前端URL,用于vue前端页面路径',
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID',
  `icon` varchar(32) DEFAULT NULL COMMENT '图标,用于前端菜单、按钮显示的图标',
  `sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序值',
  `keep_alive` char(1) DEFAULT '0' COMMENT '用于前端页面是否开启缓存,0-开启,1- 关闭',
  `type` char(1) DEFAULT NULL COMMENT '菜单类型 (0菜单 1按钮)',
  `del_flag` char(1) DEFAULT '0' COMMENT '逻辑删除标记(0--正常 1--删除)',
  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='菜单权限表';

CREATE TABLE `sys_dept` (
  `dept_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父级id',
  `name` varchar(50) DEFAULT NULL COMMENT '部门名称',
  `ancestors` varchar(100) DEFAULT NULL COMMENT '所有祖级id包括自己,逗号隔开',
  `sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
  `del_flag` char(1) DEFAULT '0' COMMENT '是否删除  1:已删除  0:正常',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `update_by` varchar(64) DEFAULT NULL COMMENT '更新人',
  PRIMARY KEY (`dept_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='部门管理';


CREATE TABLE `sys_role` (
  `role_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(64) NOT NULL,
  `role_code` varchar(64) NOT NULL COMMENT '角色标识,用于接口判断角色权限的',
  `role_desc` varchar(255) DEFAULT NULL COMMENT '角色描述',
  `del_flag` char(1) DEFAULT '0' COMMENT '删除标识(0-正常,1-删除)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `update_by` varchar(64) DEFAULT NULL COMMENT '修改人',
  `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
  PRIMARY KEY (`role_id`),
  UNIQUE KEY `role_idx1_role_code` (`role_code`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统角色表';

CREATE TABLE `sys_role_menu` (
  `role_id` bigint(20) NOT NULL,
  `menu_id` bigint(20) NOT NULL,
  PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='角色菜单表';

CREATE TABLE `sys_user_role` (
  `user_id` bigint(20) NOT NULL,
  `role_id` bigint(20) NOT NULL,
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户角色表';


-- 这里缺少一个 sys_role_dept 表,pig 里面是没有的,我们后续追加~
//2.根据那五张表添加实体类,SysDept、SysMenu、SysRole 继承自定义的实体基类 BaseEntity,SysRoleMenu、SysUserRole继承 mybatisplus 的 Model 类

//其中 id 要添加 @TableId(value = "menu_id", type = IdType.AUTO) 注解,并且id要数据库自动生成的
//其中 delFlag 删除标识,要添加 @TableLogic(value = "1", delval = "0") 注解,这样能使用 mps 的逻辑删除和逻辑查询,默认值和删除值也可以通过配置设置。【该注解只对自动注入的 sql 起效,自己在mapper.xml中写的sql不生效】

//拿 SysDept 举例
@Data
@EqualsAndHashCode(callSuper = true)
public class SysDept extends BaseEntity {

    private static final long serialVersionUID = 1L;

    @TableId(value = "dept_id", type = IdType.AUTO)
    private Long deptId;

    /**
     * 部门名称
     */
    private String deptName;

    /**
     * 排序
     */
    private Integer sortOrder;

    /**
     * 父级部门id
     */
    private Long parentId;

    /**
     * 所有祖级id包括自己,逗号隔开
     */
    private String ancestors;

    /**
     * 是否删除 -1:已删除 0:正常
     */
    @TableLogic
    private String delFlag;
}
//3.添加菜单和部门的基础增删改查;
//(1)这里的 service 类中的增删改查使用 mps 自带的,也就是 ServiceImpl 自带的 save()/update()等方法;为了方便记忆,我们自定义的方法名都要带有实体类名字,那么只要方法名中没有带有实体类名就是 mps 提供的【以后用多了就记住了】
//(2)按条件查询时使用 Wrappers 方式
//(3)如果有需要树形结构,使用 hutool 的 TreeUtil 方式,这个很方便,而且可以自由追加属性~
//4.添加角色的增删改查,同时需要添加角色-菜单表的业务类和mapper接口;
//(1)角色里面包含了操作权限和数据权限和角色基本内容,由于角色的操作权限和数据权限关乎用户的权限,而角色基本内容是不会的,所以就将这两个操作分开,并且当修改权限的时候,清除已缓存的认证用户信息和用户的菜单权限【未开启缓存可以不用清除】;修改角色基本信息的时候不用清除;而这就需要用到操作角色-部门、角色-菜单关联表操作【这里先不操作角色-部门,留到后面数据权限时操作】;
//(2)所以对于角色业务来说,增改查业务都是操作角色表,角色权限业务是操作角色-菜单表,而删除角色是操作这两张表
//(3)由于角色的基础增改查都是单表,所以不用重写 service 方法,直接在controller中使用 mps 就行;删除和保存角色的权限时,就需要涉及到两个表操作了,记得加上 @Transactional 注解,防止 sql 语句报错能进行回滚【部门和菜单的其实也可以和角色一样的~】
//5.修改用户VO、DTO类,原来并没有添加权限的相关内容;
//这里原来按照 pig 项目设计时,除用户实体类外,还有两个dto和两个vo类,但是使用逻辑有些乱,所以我又重新梳理了一下,
//除实体类外,我们还需要前端传值用于增改等的UserDTO(包含用户基本和权限、角色),还需要用户业务查询的UserVO(包含用户基本的和角色),还需要用户认证信息的UserInfoVO(包含用户基本和权限、角色);
//6.添加用户-角色表的操作逻辑,修改用户的增删改查,添加用户的角色部门权限等内容;
//(1)增删改查都要涉及到用户-角色表的关联操作;例如添加用户时,需要同时保存关联的用户-角色表,这里就需要给业务方法添加 @Transactional 注解,当出现异常是可以回滚~
//(2)其中分页查询需要查询出角色信息,这就涉及到表关联查询;可以使用 mps 的 collection 关联查询
//(3)由于重改了一下用户信息的dto/vo表,记得将所有涉及到的都修改!!!
//(4)用户登录成功回哪到token,然后根据token获取自己的用户及权限,且认证用户的权限permissions只查询按钮的权限;而菜单和目录的权限是单独查询的;
//(5)还需要在PigUserDetailsService里面设置上角色和权限


这里会用到 SysUserMapper.xml SysRoleMapper.xml SysMenuMapper.xml ~
//7.添加登录成功后获取用户菜单列表的方法;
//(1)当前菜单分为顶部菜单和左侧菜单,可以理解为顶部为一级,左侧为二级,然后后以此类推。这样的话,用户认证成功后,获取菜单权限可以分好几种:1.返回全部权限,就是根据角色搜索到用户所有的权限;2.返回默认的一个顶部的二级左侧权限,和顶部所有的一级列表。
//第一种通过一个接口入参parentid,仅查询当前用户权限祖级包含parentid就可以,只过滤掉按钮类型;第二种是在第一种基础上过滤掉按钮和顶级菜单,并追加了一个返回顶部一级菜单列表的接口;

//我们这例只实现第一种的。
//8.添加 security 注解 @PreAuthorize 需要的鉴权的类;
//(1)我们使用 security 提供的基于注解鉴权的方式,首先要开启注解鉴权,先前在 @EnablePigResourceServer 注解里面已经设置为开启了:@EnableGlobalMethodSecurity(prePostEnabled = true);
//(2)然后在需要鉴权的方法上面加上 @PreAuthorize("@pms.hasPermission('sys_user_add')") 注解,其中 'sys_user_add' 填写能访问该方法的权限唯一标识集合【权限标识也就是菜单表里面】;
//(3)创建鉴权类,逻辑就是拿到可访问该方法的标识集合,然后判断当前用户是否有这些标识,如果有则直接通过;如果没有则会抛出异常 AccessDeniedException。记得将该类注入到容器中

测试:

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

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

//9.如果@PreAuthorize 中鉴权失败接口就调用失败,会抛出 AccessDeniedException 异常,后端要返回清晰地说明,那么我们就添加全局异常处理~
//(1)创建一个类 GlobalExceptionHandle ,添加 @RestControllerAdvice 注解,然后创建方法,入参是要捕获的异常类,并给方法加上 @ExceptionHandler(AccessDeniedException.class) 注解,参数是要捕获的异常类;

//举个例子:

@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@RestControllerAdvice
public class GlobalExceptionHandle {

    public GlobalExceptionHandle(){
        System.out.println();
    }


    /**
     * @Description: AccessDeniedException
     * @param e
     * @Return: com.pig4cloud.pig.common.core.util.R
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public R handleAccessDeniedException1(AccessDeniedException e) {
        String msg = "AbstractAccessDecisionManager.accessDenied"+ e.getMessage();
        log.warn("拒绝授权异常信息 ex={}", msg);
        return R.failed(e.getLocalizedMessage());
    }

}

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

并且还没有报错信息

遇到的问题:

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开始的列表!!!


{
    //这四个是我们提供给 TreeNode 的属性的
    "id": "8",
    "parentId": "0",
    "weight": 62,
    "name": "法整活场",
    //这两个是扩展属性,放到 map 里面的
    "ancestors": "0,",
    "createTime": "2022-11-01 07:44:18",
    //如果有子级,就一定会有的属性
    "children": [
        {
            "id": "9",
            "parentId": "8",
            "weight": 33,
            "name": "只111增属证",
            "ancestors": "0,8,",
            "createTime": "2022-11-01 07:45:32"
        }
    ]
}

3.mps 的 分页查询 IPage

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

{
    "records": [
        {...}
    ],
    "total": 5,
    "size": 3,
    "current": 1,
    "orders": [],
    "optimizeCountSql": true,
    "searchCount": true,
    "countId": null,
    "maxLimit": null,
    "pages": 2
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值