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='菜单表';
/*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;
/*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='角色表';
/*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;
编写SQL语句
根据userId查询,perms对应的role和menu都必须是正常状态的。
select * from sys_user_role where user_id = #{userId}
如果只是像上面这么查询,那么就只能查到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;
这样做还不够,我们还没有查找到角色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
再稍微优化一下,我们并不需要查询出这么多信息,根据需求我们只需要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
要注意的是我们查询出来的结果需要去重,原因是因为每一个用户可以有多个角色,而每一个角色可能拥有的权限会有相同的地方,所以要用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;
}
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);
}
由于mybatis-plus默认的方法不能满足查询的需求,所以我们这里是自定义方法,所以需要创建对应的mapper文件,定义对应的sql语句。这个对应的mapper文件应该放在resources目录下的mapper文件夹中。
<?xml version="1.0"encoding="UTF-8"?>
<!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>
除此之外,我们还需要告诉mybatis-plus我们的mapper文件存放在什么地方,这里要配置yaml。
mybaits-plus:
mapperlocations: classpath*:/mapper/**/*. xml
接下来我们要在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<String> list = new ArrayList<>(Arrays.asList("test","admin"));
List<String> list = menuMapper.selectPermsByUserId(user.getId());
//把数据封装成UserDetails返回
return new LoginUser(user, list);
}
}
我们还需要在controller里对这个鉴权做一个测试,将3.2.1中的代码稍作修改,将test这个权限改成数据库中有的权限。我们在这里规定了用户访问受限资源需要的权限,后续只要修改用户的权限就可以灵活实现访问。
@RestController
public class Hellocontroller {
@RequestMapping("/hello")
@PreAuthorize("hasAuthority('test')")
public String hel1o(){
return "hello";
}
}
评论