上篇博文中我们已经实现了登录拦截,接下来我们继续补充代码,实现权限的认证
【权限认证流程】
一、RBAC权限模型
什么事RBAC权限模型?
RBAC权限模型(Role-Based Access Control)即:基于角色的权限访问控制。在RBAC中,权限与角色关联,用户通过赋予角色而获得相应角色的权限。故我们需要如下几张表:
用户表:系统接口及访问的操作者
权限表:能够访问某接口或者做某操作的授权资格
角色表:具有一类相同操作权限的用户的总称
用户角色表:用户角色相关联的表
角色权限表:角色与权限相关联的表
二、创建对应的数据库表
可根据项目需要增加或删减、修改对应的表字段
sys_user
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'ID',
`nick_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
`user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
`gender` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别',
`phone` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码',
`email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
`role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色ID',
`is_admin` bit(1) NULL DEFAULT b'0' COMMENT '是否为admin账号',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`enabled` bit(1) NULL DEFAULT NULL COMMENT '是否启用',
`create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建者',
`update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统用户' ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
sys_role
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'ID',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称',
`level` int(0) NULL DEFAULT NULL COMMENT '角色级别',
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
`create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建者',
`update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
sys_menu
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'ID',
`pid` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '上级菜单ID',
`sub_count` int(0) NULL DEFAULT 0 COMMENT '子菜单数目',
`type` int(0) NULL DEFAULT NULL COMMENT '菜单类型',
`title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单标题',
`menu_sort` int(0) NULL DEFAULT NULL COMMENT '排序',
`icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图标',
`path` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '链接地址',
`cache` bit(1) NULL DEFAULT b'0' COMMENT '缓存',
`hidden` bit(1) NULL DEFAULT b'0' COMMENT '隐藏',
`permission` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限',
`create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建者',
`update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新者',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
`update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 118 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统菜单' ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
sys_user_role
DROP TABLE IF EXISTS `sys_users_roles`;
CREATE TABLE `sys_users_roles` (
`user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户ID',
`role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`, `role_id`) USING BTREE,
INDEX `user_id`(`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户角色关联' ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
sys_role_menu
DROP TABLE IF EXISTS `sys_roles_menus`;
CREATE TABLE `sys_roles_menus` (
`role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色ID',
`menu_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`role_id`, `menu_id`) USING BTREE,
INDEX `FKcngg2qadojhi3a651a5adkvbq`(`role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色菜单关联' ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
三、生成5张基础表对应的Java代码
可自行书写或使用mybatis工具进行生成,也可参考之前的博文
四、鉴权实现
1.创建PermissionMapper.xml
变现sql根据用户ID查询角色对应的权限
<?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="com.hng.mapper.PermissionMapper">
<select id="selectPermsByUserId" resultType="java.lang.String">
SELECT
DISTINCT m.permission
FROM
sys_users_roles ur
LEFT JOIN sys_role r ON ur.role_id = r.id
LEFT JOIN sys_roles_menus rm ON ur.role_id = rm.role_id
LEFT JOIN sys_menu m ON m.id = rm.menu_id
WHERE
user_id = #{userId}
</select>
</mapper>
2.创建PermissionMapper
package com.hng.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hng.entity.SysMenu;
import java.util.List;
/**
* <p>
* 系统菜单 Mapper 接口
* </p>
*
* @author 郝南过
* @since 2023-12-19
*/
public interface PermissionMapper extends BaseMapper<SysMenu> {
List<String> selectPermsByUserId(String userId);
}
3.改造SecurityUser
(1)改造构造函数增加权限集合
private List<String> permissions;
public SecurityUser(SysUser user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
(2)改造getAuthorities
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//将permissions中的String类型的权限信息封装成SimpleGrantedAuthority对象
if(authorities!=null){
return authorities;
}
authorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return authorities;
}
4.改造UserDetailsServiceImpl
修改loadUserByUsername方法,使用Permission增加权限查询
package com.hng.config.security;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hng.config.exception.customize.EntityNotFoundException;
import com.hng.modules.system.entity.SysUser;
import com.hng.modules.system.mapper.PermissionMapper;
import com.hng.modules.system.mapper.SysMenuMapper;
import com.hng.modules.system.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* @Author: 郝南过
* @Description: 将Security拦截的用户名密码改为数据库中已有的用户名密码,Security自己校验密码,默认使用PasswordEncoder,格式为{id}password(id代表加密方式),
* 一般不采用此方式,SpringSecurity提供了BcryptPasswordEncoder,只需将此注入到spring容器中。SpringSecurity就会使用它进行替换校验
* @Date: 2023/12/11 11:29
* @Version: 1.0
*/
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private SysUserService sysUserService;
@Resource
private PermissionMapper permissionMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户信息
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(SysUser::getUserName,username);
SysUser user = sysUserService.getOne(queryWrapper);
if(Objects.isNull(user)){
throw new RuntimeException("用户名或密码错误");
}
//查询授权信息
List<String> permissions = permissionMapper.selectPermsByUserId(user.getId());
//判断用户是否是管理员,如果是管理员添加admin权限
if(user.getIsAdmin()){
permissions.add("admin");
}
return new SecurityUser(user,permissions);
}
}
5.自定义权限检测
可直接使用已有的权限检测@PreAuthorize(“hasAuthority('自定义字符串')”),hasAuthority是系统提供的,可直接使用,也可以自定义。以下为自定义,为方便直接判断admin用户,并给admin用户授予所有权限。
PermissionConfig
package com.hng.config.security;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 自定义权限检测
*/
@Component(value = "ex")
public class PermissionConfig {
public Boolean check(String... permissions) {
// 获取当前用户的所有权限
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
List<String> exPermissions = securityUser.getPermissions();
// 判断当前用户的所有权限是否包含接口上定义的权限
return exPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(exPermissions::contains);
}
}
6.在SecurityConfig中添加注解
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启权限权限认证
7.在Cotroller中添加对应的权限字符
如果未自定义权限检测使用注解@PreAuthorize(“hasAuthority('自定义字符串')”),本篇博文中的例子已经自定义为@PreAuthorize("@ex.check('自定义字符串')")
SysUserController 示例
package com.hng.controller;
import lombok.RequiredArgsConstructor;
import io.swagger.annotations.ApiOperation;
import com.hng.config.response.ResponseResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import io.swagger.annotations.ApiParam;
import com.hng.entity.SysUser;
import com.hng.service.SysUserService;
/**
* <p>
* 系统用户 前端控制器
* </p>
*/
@Slf4j
@Api(value = "SysUser", tags = "SysUser")
@RestController
@RequiredArgsConstructor //Lombok的一个注解,简化@Autowired
@RequestMapping("/sys-user")
public class SysUserController {
private final SysUserService sysUserService;
@PostMapping("/getList")
@ApiOperation("sysUser列表查询")
@PreAuthorize("@ex.check('SysUser:list')")
public ResponseResult queryAllSysUser(@Validated @RequestBody SysUser sysUser){
List<SysUser> list = sysUserService.queryAll(sysUser);
return ResponseResult.success(list);
}
@PostMapping("/add")
@ApiOperation("新增SysUser")
@PreAuthorize("@ex.check('SysUser:add')")
public ResponseResult addSysUser(@Validated @RequestBody SysUser sysUser){
sysUserService.addSysUser(sysUser);
return ResponseResult.success();
}
/**
* 根据ID查询数据
* @param id ID
*/
@GetMapping("getById/{id}")
@ApiOperation("sysUser根据Id查询")
@ResponseBody
@PreAuthorize("@ex.check('SysUser:getById')")
public ResponseResult getById(@PathVariable Integer id) {
return ResponseResult.success(sysUserService.getSysUserById(id));
}
/**
* 更新数据
* @param sysUser 实体对象
*/
@PutMapping("update")
@ApiOperation("sysUser更新")
@ResponseBody
@PreAuthorize("@ex.check('SysUser:edit')")
public ResponseResult update(@Validated @RequestBody SysUser sysUser) {
sysUserService.updateSysUser(sysUser);
return ResponseResult.success();
}
/**
* 删除数据
* @param id ID
*/
@DeleteMapping("delete/{id}")
@ApiOperation("sysUser根据Id删除")
@ResponseBody
@PreAuthorize("@ex.check('SysUser:del')")
public ResponseResult delete(@PathVariable Integer id) {
sysUserService.deleteSysUser(id);
return ResponseResult.success();
}
}
五.数据库中添加数据
根据自己定义的权限字符串与用户进行数据添加
六.测试
使用pomstman进行测试,注此时可以测试上篇博文中的权限认证异常处理器