# 1 基于rbac+AOP实现权限控制 rbac:基于角色控制权限 每一个公司都不一样 rbac基础班 有不同的变种版 ## 1.1 rbac之5张表 \~\~\~sql -- 用户 drop table if exists tb_user; CREATE TABLE tb_user ( id bigint(20) NOT NULL primary key AUTO_INCREMENT, username varchar(20) comment '用户名', password varchar(20) comment '密码', age int comment '年龄', birth timestamp COMMENT '生日', create_time timestamp COMMENT '创建时间' ) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8; insert into tb_user values (null,'zhangsan','123',12,'1991-03-03 12:13:14',now()), (null,'lisi','123',22,'1992-02-03 10:13:14',now()), (null,'wangwu','123',22,'1992-02-03 10:13:14',now()), (null,'admin','123',22,'1992-02-03 10:13:14',now()); -- 角色 drop table if exists tb_role; CREATE TABLE tb_role ( id bigint(20) NOT NULL primary key AUTO_INCREMENT, role_name varchar(20) comment '角色名', role_code varchar(20) comment '角色code', description varchar(200) comment '角色描述' ) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8; insert into tb_role values (null,'管理员','admin','超级管理员'), (null,'书籍管理员','admin_book','书籍增删查改'), (null,'类型管理员','admin_type','书籍类型增删查改'), (null,'用户管理员','admin_user','用户增删查改'); -- 权限/菜单 drop table if exists tb_permission; CREATE TABLE tb_permission( id bigint(20) NOT NULL primary key AUTO_INCREMENT, pid bigint(20) comment '父id,用来描述该子菜单归属哪一个父菜单,前端可以根据父子关系来动态展示菜单', permission_name varchar(20) comment '权限名', permission_code varchar(20) comment '权限code', description varchar(200) comment '权限描述', router varchar(50) comment '路由' ) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8; insert into tb_permission values (null,null,'书籍管理','book','书籍管理','book'), (null,10000,'书籍添加','book_add','书籍添加','/book/bookAdd'), (null,10000,'书籍删除','book_del','书籍删除',null), (null,10000,'书籍查询','book_select','书籍查询','/book/bookList'), (null,10000,'书籍修改','book_upd','书籍修改',null), (null,null,'类型管理','type','类型管理','type'), (null,10005,'类型添加','type_add','类型添加','/book/typeAdd'), (null,10005,'类型删除','type_del','类型删除',null), (null,10005,'类型查询','type_select','类型查询','/book/typeList'), (null,10005,'类型修改','type_upd','类型修改',null), (null,null,'用户管理','user','用户管理','user'), (null,10010,'用户添加','user_add','用户添加','/book/userAdd'), (null,10010,'用户删除','user_del','用户删除',null), (null,10010,'用户查询','user_select','用户查询','/book/userList'), (null,10010,'用户修改','user_upd','用户修改',null); -- 测试追加三级菜单 -- insert into tb_permission values -- (null,10001,'书籍添加1','book_add1','书籍添加1','/book/bookAdd1'), -- (null,10001,'书籍添加2','book_add2','书籍添加2','/book/bookAdd2'); -- 用户-角色表 drop table if exists tb_user_role; CREATE TABLE tb_user_role( id bigint(20) NOT NULL primary key AUTO_INCREMENT, uid bigint(20) comment '用户id', rid bigint(20) comment '角色id' ) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8; insert into tb_user_role values (null,10000,10001), -- zhangsan拥有的角色: 书籍管理员 (null,10001,10002), -- lisi拥有的角色: 类型管理员 (null,10002,10001), -- wangwu拥有的角色: 书籍管理员 (null,10002,10002), -- wangwu拥有的角色: 类型管理员 (null,10003,10000); -- admin拥有的角色: 管理员 -- 角色-权限表 drop table if exists tb_role_permission; CREATE TABLE tb_role_permission( id bigint(20) NOT NULL primary key AUTO_INCREMENT, rid bigint(20) comment '角色id', pid bigint(20) comment '权限id' ) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8; insert into tb_role_permission values (null,10001,10000), (null,10001,10001), (null,10001,10002), (null,10001,10003), (null,10001,10004), (null,10002,10005), (null,10002,10006), (null,10002,10007), (null,10002,10008), (null,10002,10009), (null,10003,10010), (null,10003,10011), (null,10003,10012), (null,10003,10013), (null,10003,10014); select \* from tb_user; select \* from tb_user_role; select \* from tb_role; select \* from tb_role_permission; select \* from tb_permission; -- 根据用户查找角色 -- select t3.role_name,t3.role_code from tb_user t1 inner join tb_user_role t2 on t1.id = t2.uid inner join tb_role t3 on t2.rid= t3.id where t1.id=10003; -- -- -- 根据用户查找权限 select t5.\* from tb_user t1 inner join tb_user_role t2 on t1.id = t2.uid inner join tb_role t3 on t2.rid= t3.id inner join tb_role_permission t4 on t3.id= t4.rid inner join tb_permission t5 on t4.pid=t5.id where t1.id=10003; \~\~\~ ## 1.2 登录,保存用户信息(包含角色和权限菜单) ### 1 controller \~\~\~java @RequestMapping("/login") public Result login(@RequestBody UserDto userDto, HttpSession session){ User user=userService.login(userDto); if(user==null){ return Result.error("账号或密码错误"); } //保存session,这里直接已通过参数注入HttpSession即可使用 session.setAttribute("user",user); return Result.success(user); } \~\~\~ userDto: \~\~\~java @Data public class UserDto { private String username; private String password; \~\~\~ ### 2 service \~\~\~java @Override public User login(UserDto userDto) { User user= userDao.getUserByUsername(userDto); if(user!=null){ //再根据密码来查找 if(user.getPassword().equals(userDto.getPassword())){ //查询用户对应的角色集合 List roleList = userDao.getRoleListByUserId(user.getId()); user.setRoleList(roleList); //查询用户对应的权限集合 List permissionList; //单独判断超级管理员 boolean isAdmin=roleList.stream().anyMatch(role -\> role.getRoleCode().equals("admin")); if(isAdmin){ //超管 permissionList = permissionDao.selectAll(); }else{ //非超管 permissionList = userDao.getPermissionListByUserId(user.getId()); } //扁平化权限数据转换为具有层级结构的数据,方便前端展示 user.setPermissionList(getPermissionList(permissionList)); return user; } } return null; } //扁平化权限数据转换为具有层级结构的数据,方便前端展示 public List getPermissionList(List permissions) { //新建一个map集合 Map permissionMap=new HashMap\<\>(); //遍历permissions集合,为permissionMap集合填充 for(Permission p:permissions){ permissionMap.put(p.getId(),p); } //为每一个父节点追加children属性,并填充子集合 //遍历所有权限,对于每个有父节点(pid不为空)的权限,找到其父权限对象, //并将当前权限添加到父权限的子权限列表中 for (Permission per : permissions) { if (per.getPid() != null) { Permission parent = permissionMap.get(per.getPid()); if (parent != null) { parent.addChild(per); } } } //只筛选父节点 List result = permissions.stream() .filter(per -\> per.getPid() == null) .collect(Collectors.toList()); return result; } \~\~\~ ### 3 dao #### UserDao.java \~\~\~java /\*\* \* 根据uid查找权限 \* @param uid \* @return \*/ List getPermissionsByUid(Long uid); /\*\* \* 根据用户id查找角色集合 \* @param uid \* @return \*/ List getRoleListByUserId(Long uid); /\*\* \* 根据用户名查询用户 \* @param username \* @return \*/ User getUserByUsername(String username); \~\~\~ #### UserDao.xml \~\~\~xml select t5.permission_name,t5.permission_code,t5.id,t5.pid from tb_user t1 inner join tb_user_role t2 on t1.id=t2.uid inner join tb_role t3 on t2.rid=t3.id inner join tb_role_permission t4 on t3.id=t4.rid inner join tb_permission t5 on t5.id=t4.pid where t1.id=#{uid} select t3.role_name,t3.role_code from tb_user t1 inner join tb_user_role t2 on t1.id = t2.uid inner join tb_role t3 on t2.rid= t3.id where t1.id=#{uid} select \* from tb_user where username =#{username} \~\~\~ #### PermissionDao.java \`\`\`java List selectAll(); \`\`\` #### PermissionDao.xml \~\~\~xml select id, pid, permission_name, permission_code, description, router from tb_permission \~\~\~ ### 4 User \~\~\~java @Data public class User { private Long id; private String username; @JsonIgnore private String password; private Integer age; @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8") private Date birth; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date createTime; //权限集合 List permissionList; //角色集合 List roleList; } \~\~\~ ## 1.3 鉴权 ### 0 介绍 拦截用户直接访问不属于自己权限的请求 ### 1 自定义注解 PermissionAnno !\[image-20250929010152777\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250929010152.png) \~\~\~java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface PermissionAnno { String value() default ""; } \~\~\~ ### 2 切面 PermissionAspect !\[image-20250929010203696\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250929010203.png) \~\~\~java package com.woniu.aop; import com.woniu.anno.PermissionAnno; import com.woniu.domain.User; import com.woniu.exception.ErrCode; import com.woniu.exception.ServiceException; import jakarta.servlet.http.HttpSession; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.lang.reflect.Method; package com.woniu.aop; import com.woniu.anno.PermissionAnno; import com.woniu.domain.User; import com.woniu.exception.ErrCode; import com.woniu.exception.ServiceException; import jakarta.servlet.http.HttpSession; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.lang.reflect.Method; @Component @Aspect public class PermissionAspect { @Pointcut("@annotation(com.woniu.anno.PermissionAnno)") public void PermissionMethod(){} @Around("PermissionMethod()") public Object PermissionCheck(ProceedingJoinPoint joinPoint) throws Throwable { //获取自定义注解的权限名 MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature(); Method method = methodSignature.getMethod(); PermissionAnno permissionAnno = method.getAnnotation(PermissionAnno.class); //获得session //RequestContextHolder是Spring提供的工具类,用于获取当前请求上下文 ServletRequestAttributes servletRequestAttributes =(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); HttpSession session = servletRequestAttributes.getRequest().getSession(); Object obj = session.getAttribute("user"); if(obj==null){ throw new ServiceException(ErrCode.NO_LOGIN.getCode(), ErrCode.NO_LOGIN.getMsg()); } //强转user User user=(User)obj; //对角色admin单独判断 boolean hasAdminRole = user.getRoleList().stream() .anyMatch(role -\> role.getRoleCode().equals("admin")); if(hasAdminRole){ //放行 Object proceed = joinPoint.proceed(); return proceed; } //对比是否拥有该权限 boolean hasPermission = user.getPermissionList().stream() .anyMatch(permission -\> permission.getPermissionCode().equals(permissionAnno.value())); if(!hasPermission){ throw new ServiceException(ErrCode.NO_AUTHORIZATION.getCode(), ErrCode.NO_AUTHORIZATION.getMsg()); } //放行 Object proceed = joinPoint.proceed(); return proceed; } } \~\~\~ ### 3 ErrCode \~\~\~java @Getter public enum ErrCode { NO_LOGIN(401,"未登录"), NO_AUTHORIZATION(403,"无权限"); private final int code; private final String msg; ErrCode(int code, String msg) { this.code = code; this.msg = msg; } } \~\~\~ ### 4 controller层上添加对应属性的PermissionAnno BookController \~\~\~java @RestController @RequestMapping("/book") public class BookController { @PermissionAnno("book_add") @RequestMapping("/save") public Result save(){ System.out.println("书籍添加"); return Result.success(); } \~\~\~ TypeController \~\~\~java @RestController @RequestMapping("/type") public class TypeController { @PermissionAnno("type_add") @RequestMapping("/save") public Result save(){ System.out.println("类型添加"); return Result.success(); } \~\~\~ ### 5 postman测试 !\[image-20250929010443809\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250929010443.png) !\[image-20250929010658434\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250929010658.png) 1 登录后测试以下请求 2 切换不同人登录时最好先关闭服务(清空session) !\[image-20250929010713199\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250929010713.png) !\[image-20250929010720197\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/web/wangyongcheng/20250929010720.png) ## 1.4 vue 实现 动态菜单(可选) ### 介绍 介绍动态菜单:菜单由后端动态获取,方便维护,同时也实现了权限控制(用户只能获取和展示自己拥有权限的组件菜单) ### userApi.js \~\~\~js import axios from "@/http/request.js"; //登录 const userApi={ //登录 login(data){ return axios({ method: "post", url: "/user/login", data, }); } } export default userApi; \~\~\~ ### Login.vue \~\~\~vue
登录
===
登录 重置 \~\~\~ ### Aside.vue \~\~\~vue \~\~\~ ### MenuComponent.vue(递归组件) \~\~\~vue {{ item.permissionName }} {{ item.permissionName }} \~\~\~ ## 1.5 vue 实现动态路由(可选) 适用场景: 1多租户系统 2权限敏感的系统 3需要动态调整导航结构的系统 不适用场景: 1固定功能模块 2性能要求极高的应用
04_RBAC+AOP-权限(动态菜单)
https://xiaochenblog.icu/archives/04_rbac-aop-quan-xian-dong-tai-cai-dan
评论