• 【Spring Security】安全框架学习(九)


    3.2.3 从数据库查询权限信息
    3.2.3.1 RBAC权限模型

    RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。

    一个用户可能有多个权限,所以我们会有一个权限表。正常都是用户关联角色,角色关联权限决定可以进行哪些操作。一个权限对应多个用户,一个用户也会有多个权限,看起来是一个多对多的关系,但是从用户的角度看是一个一对多的关系。

    在开发的过程中,给一个用户挨个赋予权限显然比较麻烦,所以我们可以建立起一个角色表,直接给用户赋予角色信息,即可将对应的权限通过角色间接的给用户。

    角色表如何与权限表关联起来呢,我们通过一个角色权限关联表来将它们两个关联起来,表示关联关系。

    同时,一个用户可以有多个角色,一个角色也有多个用户,这样的多对多的关系同样可以用一个用户角色关联表关联起来。

    3.2.3.2 准备工作
    /*Table structure for table sys_menu */
    DROP TABLE IF EXISTS sys_menu;
    CREATE TABLE `sys_menu` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `menu_name` varchar(64) NOT NULL DEFAULT'NULL' COMMENT'菜单名',
    `path` varchar(200) DEFAULT NULL COMMENT'路由地址',
    `component` varchar(255) DEFAULT NULL COMMENT'组件路径',
    `visible` char(1) DEFAULT '0' COMMENT '菜单状态(O显示 1隐藏)',
    `status`char(1) DEFAULT'O'COMMENT '菜单状态(O正常 1停用)',
    `perms` varchar(100) DEFAULT NULL COMMENT'权限标识',
    `icon` varchar(100) DEFAULT'#'COMMENT'菜单图标',
    `create_by` bigint(20) DEFAULT NULL,
    `create_time` datetime DEFAULT NULL,
    `update_by` bigint(20) DEFAULT NULL,
    `update_time` datetime DEFAULT NULL,
    `del_flag` int(11) DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
    PRIMARY KEY (`id`)
    )ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    /*Table structure for table sys_user_role */
    DROP TABLE IF EXISTS `sys_user_role`;
    CREATE TABLE sys_user_role (
    `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT'用户id',
    `role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT'角色id',
    PRIMARY KEY (`user_id`,`role_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    /*Table structure for table sys_role */
    DROP TABLE IF EXISTS `sys_role`;
    CREATE TABLE `sys_role` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `name` varchar(128) DEFAULT NULL,
    `role_key` varchar(100) DEFAULT NULL COMMENT'角色权限字符串',
    `status`char(1) DEFAULT '0' COMMENT '角色状态(O正常 1停用)',
    `del_flag` int(1) DEFAULT '0' COMMENT 'del_flag',
    `create_by` bigint(20) DEFAULT NULL,
    `create_time` datetime DEFAULT NULL,
    `update_by` bigint(20) DEFAULT NULL,
    `update_time` datetime DEFAULT NULL,
    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
    PRIMARY KEY (`id`)
    )ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    /*Table structure for table sys_role_menu* */
    DROP TABLE IF EXISTS `sys_role_menu`;
    CREATE TABLE `sys_role_menu` (
    `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
    `menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '菜单id',
    PRIMARY KEY (`role_id`,`menu_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    编写SQL语句

    根据userId查询,perms对应的role和menu都必须是正常状态的。

    select * from sys_user_role where user_id = #{userId}
    
    • 1

    如果只是像上面这么查询,那么就只能查到user_id 和role_id,查不到其他信息,所以我们要使用联表查询。

    select * from sys_user_role ur left join sys_role r on ur.role_id = r.id where user_id = #{userId} and r.status=0;
    
    • 1

    这样做还不够,我们还没有查找到角色id对应的权限。

    select * 
    from sys_user_role ur 
    	left join sys_role r on ur.role_id = r.id
    	left join sys_role_menu rm on ur.role_id = rm.role_id
    	left join sys_menu m on m.id = rm.menu_id
    where user_id = #{userId} 
    	and r.status=0
    	and m.status=0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    再稍微优化一下,我们并不需要查询出这么多信息,根据需求我们只需要perms这一个字段。

    select distinct m.perms
    from sys_user_role ur 
    	left join sys_role r on ur.role_id = r.id
    	left join sys_role_menu rm on ur.role_id = rm.role_id
    	left join sys_menu m on m.id = rm.menu_id
    where user_id = #{userId} 
    	and r.status=0
    	and m.status=0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    要注意的是我们查询出来的结果需要去重,原因是因为每一个用户可以有多个角色,而每一个角色可能拥有的权限会有相同的地方,所以要用distinct来去重。

    编写实体类

    因为perms在sys_menu这张表中,所以只用创建这一个实体类就行。

    package domain;
    
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import Tombok.NoArgsConstructor;
    
    import java.io.Serializable;
    import java.util.Date;
    
    
    @TableName(value="sys_menu")
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class Merlu implements Serializable {
    	private static final long serialVersionUID = -54979041104113736L;
    	@TableId
    	private Long id;
    	/**
    	 * 菜单名
    	 */
    	private String menuName;
    	/**
     	 * 路由地址
    	 */
    	private String path;
    	/**
    	 * 组件路径
    	 */
    	private String component;
    	/**
    	 * 菜单状态(0显示1隐藏)
    	 */
    	private String visible;
    	/**
    	 * 菜单状态(0显示1隐藏)
    	 */
    	private String visible;
    	/**
    	 * 菜单状态(0正常1停用)
    	 */
    	private String status;
    	/**
    	 * 权限标识
    	 */
    	private String perms;
    	/**
     	 * 菜单图标
    	 */
    	private String icon;
    	private Long createBy;
    	private Date createTime;
    	private Long updateBy;
    	private Date updateTime;
    	/**
    	 * 是否删除(0未删除1已删除)
    	 */
    	private Integer delFlag;
    	/**
    	 * 备注
    	 */
    	private String remark;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    3.2.3.3 代码实现

    我们只需要根据用户id去查询到其所对应的权限信息即可。所以我们可以先定义个mapper,其中提供一个方法可以根据userid查询权限信息。

    package mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import domain.Menu;
    import java.util.List;
    
    public interface MenuMapper extends BaseMapper<Menu> {
    	List<String> selectPermsByUserId(Long id);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    由于mybatis-plus默认的方法不能满足查询的需求,所以我们这里是自定义方法,所以需要创建对应的mapper文件,定义对应的sql语句。这个对应的mapper文件应该放在resources目录下的mapper文件夹中。

    
    DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="mapper.MenuMapper">
    	<select id="selectPermsByUserId" resultType="java.lang.String">
        	select distinct m.perms
    		from sys_user_role ur 
    			left join sys_role r on ur.role_id = r.id
    			left join sys_role_menu rm on ur.role_id = rm.role_id
    			left join sys_menu m on m.id = rm.menu_id
    		where user_id = #{userId} 
    			and r.status=0
    			and m.status=0
    	select>
    mapper>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    除此之外,我们还需要告诉mybatis-plus我们的mapper文件存放在什么地方,这里要配置yaml。

    mybaits-plus:
      mapperlocations: classpath*:/mapper/**/*. xml
    
    
    • 1
    • 2
    • 3

    接下来我们要在UserDetailsServiceImpl中加入对应的方法

    package service.impl;
    
    import ...
    
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    	
        @Autowired
        private UserMapper userMapper;
        
        @Autowired
    	private MenuMapper menuMapper;
        
        @Override
    	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            private UserMapper 
            
            //查询用户信息
            LambdaQueryWrapper<User> queryWeapper=new LambdaQueryWrapper();
            queryWrapper.eq(User::getUserName,username);
            User user = userMapper.selectOne(queryWrapper);
            
            //如果没有查询到用户,就抛出异常
            if(Object.isNull(user)) {
                throw new RuntimeException("用户名或密码错误");
            }
            
      		//TODO 查询对应的权限信息,将之前的测试代码注释掉,然后调用menuMapper中的方法,并存入list中
    		//List list = new ArrayList<>(Arrays.asList("test","admin"));
            List<String> list = menuMapper.selectPermsByUserId(user.getId());
    
            
            //把数据封装成UserDetails返回
            return new LoginUser(user, list);
    	}
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    我们还需要在controller里对这个鉴权做一个测试,将3.2.1中的代码稍作修改,将test这个权限改成数据库中有的权限。我们在这里规定了用户访问受限资源需要的权限,后续只要修改用户的权限就可以灵活实现访问。

    @RestController
    public class Hellocontroller {
    	
        @RequestMapping("/hello")
    	@PreAuthorize("hasAuthority('test')")
    	public String hel1o(){
    		return "hello";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    特征融合与特征交互的区别
    03.爱芳地产项目小程序全栈项目经验(已上线)
    OpenCV之怀旧色、冰冻滤镜、熔铸滤镜
    【Linux详解】——共享内存
    【GESP】2023年06月图形化三级 -- 计算最终值
    主打低功耗物联网国产替代,纵行科技ZT1826芯片以速率和灵敏度出圈
    Python绘图系统19:添加时间轴以实现动态绘图
    Go 项目依赖注入wire工具最佳实践介绍与使用
    访问者模式你了解了吗?
    [附源码]java毕业设计宠物狗领养网站
  • 原文地址:https://blog.csdn.net/qq_38594872/article/details/126655853