所涉及到的技能点:
Springboot 、分页后端插件 pagehelper 、分页前端插件 twbs-pagination
首先什么是RBAC
RBAC 是基于角色的访问控制(Role-Based Access Control ),简单的来说就是不同的人有不同的权限,什么样的人干什么样的事
数据库的设计
员工表:用来存储员工信息
字段分析: id(员工编号,主键自增长)、 name(员工姓名)、
role_id(角色编号,关联查询角色)、super_admin(是否超级管理员)
角色表:用来存储员工的角色或者说职位
字段分析: id(角色编号)、 name(角色名称)
权限表:用来存储权限
字段分析: id(权限编号)、name(权限的名称)、 permission(权限的表达式,可以理解为访问的路径)
在设计两个关联表
角色与员工关联表:通过员工查询其拥有的角色
角色与权限关联表:通过角色查询它拥有的权力
项目准备
创建好pojo类、mapper接口、service类、controller类
对于权限的录入 、要么一个一个的录入到数据库中,要么使用注解的方式去标识,后面再从注解里取到他的权限
分析如果是一个一个录入到数据库中 (inser into···) ,如果权限很多,那就会显得非常繁琐麻烦,而且数据一多起来有可能会写错。
所以这里我使用注解的方式去实现。
权限注解设计与使用
创建一个注解 RequestPermission
@Target({ElementType.METHOD}) //标记注解只能再方法上使用
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestPermission {
//设置一个权限名称
String name();
//表达式
String expression();
}
在我们需要设置权限的地方添加 注解 以:员工权限举例
这里是一个员工的列表页面
我们不想任何人都可以访问我们的 添加 和 编辑 页面
所以我们就可以再除了 查询页面的方法以外的方法添加注解
再员工的 EmployeeController
下 :这里主要讲解注解是 RequestPermission
怎么使用的其他的业务逻辑可以忽略 员工查询 list 没有加注解是为了谁都可以查看
@Controller
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private IEmployeeService employeeService;
@Autowired
private IRoleService iRoleService;
// 处理分页查询员工请求
@RequestMapping("/list")
// @ModelAttribute("qo") 这个是给 参数 qo 取个别名方便前端页面回显数据拿到
// 但是这里 不写也可以名字一样了
public String list(Model model, @ModelAttribute("qo") EmployeeQueryObject qo) {
PageInfo<Employee> pageInfo = employeeService.query(qo); //这里使用的是 pagehelper 实现分页
model.addAttribute("pageInfo", pageInfo); // 将我们的分页结果集返回给前端
return "employee/list";
}
// 处理删除员工请求 /employee/delete?id=*
@RequestMapping("/delete")
//设置权限的名称为员工删除 , 表达式为 employee:delete
@RequestPermission(name="员工删除" ,expression = "employee:delete")
public String delete(Long id) {
if (id != null) {
employeeService.delete(id);
}
return "redirect:/employee/list";
}
// 处理去新增或者去修改请求 /employee/input
@RequestMapping("/input")
//设置权限的名称为员工编辑页面 , 表达式为 employee:input
@RequestPermission(name="员工编辑页面" ,expression = "employee:input")
public String input(Long id, Model model) {
if (id != null) {
Employee employee = employeeService.get(id);
model.addAttribute("employee", employee);
//如果不为空,那我就再去查看他的角色
List<Role> roleList = iRoleService.getByEmployeeId(id);
model.addAttribute("roleList",roleList);
}
//再去查询分配角色
List<Role> roles = iRoleService.listAll();
model.addAttribute("roles",roles);
return "employee/input";
}
// 处理新增请求 /employee/saveOrUpdate 参数在请求体
@RequestMapping("/saveOrUpdate")
@ResponseBody
//设置权限的名称为员工保存和修改 , 表达式为 employee:saveOrUpdate
@RequestPermission(name="员工保存和修改" ,expression = "employee:saveOrUpdate ")
public JsonResult saveOrUpdate(Employee employee,Long[] roleIds) {
// JsonResult 是自己定义的一个类 字段有 (boolean)success 和 (String)msg 用来返回一个js对象给前端接收
try {
if (employee.getId() != null) {
employeeService.update(employee,roleIds);
} else {
employeeService.save(employee,roleIds);
}
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(false,e.getMessage());
}
return new JsonResult(true,"成功保存");
}
}
简化版:
@Controller
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private IEmployeeService employeeService;
@Autowired
private IRoleService iRoleService;
@RequestMapping("/list")
public String list(Model model, @ModelAttribute("qo") EmployeeQueryObject qo) {
}
@RequestMapping("/delete")
@RequestPermission(name="员工删除" ,expression = "employee:delete")
public String delete(Long id) {
}
@RequestMapping("/input")
@RequestPermission(name="员工编辑页面" ,expression = "employee:input")
public String input(Long id, Model model) {
}
@RequestMapping("/saveOrUpdate")
@ResponseBody
@RequestPermission(name="员工保存和修改" ,expression = "employee:saveOrUpdate ")
public JsonResult saveOrUpdate(Employee employee,Long[] roleIds) {
}
}
注解的扫描和存储
那么接下来问题就到了 :
如何将我们注解上的信息保存到我们的数据库?
怎么识别方法上的注解?
再权限页面设置一个权限查询按钮刷新之后将我们的权限全部录入到数据库中
效果展示:
在我们的 PermissionService
的保存方法中
@Service
public class PermissionServiceImpl implements IPermissionService {
@Autowired
private PermissionMapper permissionMapper;
@Autowired
private ApplicationContext ac;
Permission permission = new Permission();
Set<String> set = new HashSet<>();
@Override
public void savePermission() {
// 先查询数据库 找到我们所有的权限放到Set中
List<Permission> permissions = permissionMapper.selectAll();
if (permissions!=null){
permissions.forEach((permission)->{set.add(permission.getExpression());});
}
//这里我们编写保存方法
//首先我们得先拿到我们 Controller的方法 在拿到我们方法上面的注解
//我们的容器已经有了,就在 RequestMappingHandlerMapping 里面,它扫描了全部的controller 方法
//这也是为什么拦截器能找到我们对应的方法路径
RequestMappingHandlerMapping handlerMapping = ac.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = handlerMapping.getHandlerMethods();
//我们拿到所有的value也就是 methods
Collection<HandlerMethod> methods = handlerMethodMap.values();
//得到方法上的注解 标记我们想要的注解
for (HandlerMethod method : methods) {
//判断每一个方法上有没有这个注解,如果没有就跳过
RequestPermission requestPermission = method.getMethodAnnotation(RequestPermission.class);
if (requestPermission == null)
continue;
String expression = requestPermission.expression();
//为了防止 重复存入,我们使用Set集合 的无序不可重复 特性存储
//能到这里说明是有值的,那我就拿出来,存到数据库中
if (set.contains(expression))
continue; // 如果我们存过了 那就直接跳过
//把这个 表达式 存到 set里面
set.add(expression);
String name = requestPermission.name();
permission.setName(name);
permission.setExpression(expression);
permissionMapper.insert(permission);
}
}
}
拦截的实现
接下来问题就到了 如何实现权限的拦截?
使用拦截器 编写一个 PermissionInterceptor 实现 HandlerInterceptor 接口 重写 preHandle 方法 实现拦截
再把我们写的拦截器 添加到spring中,让他生效
这里的前提是 员工要有角色 才能有权限: 这里的 员工 绑定 角色 省略了 就假定 用户只有 查询权限,没有编辑和删除权限 需要被拦截
ps : 这里是写了 员工登入功能,如果没有登入就不能走到这里,登入之后将 员工存入session ,再从session取出
判断是不是超级管理员 、 和有什么角色 ,再通过什么角色 找到 什么功能
编写自己的权限拦截器
@Component
public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//权限拦截器
// 首先我先判断它有没有权限, 先拿到他 session 的 employee 信息
Employee employee = (Employee) request.getSession().getAttribute("EMPLOYEE_IN_SESSION");
if (employee.isAdmin())
return true; //如果是超级管理员我直接放行
//为了效率和优化:将所有的权限都先查出来 放到session中
//拿到session 的roles
List<String> expressions = (List<String>) request.getSession().getAttribute("PERMISSION_IN_SESSION");
// handler 可以获取所有的方法 包括 静态ResourceHttpRequestHandler 和 非静态的HandlerMethod方法
//静态资源不需要 拦截 ,只需要拦截 Controller 方法上 有 RequestPermission 注解的方法
if (handler instanceof HandlerMethod){
//强转
HandlerMethod handlerMethod = (HandlerMethod) handler;
//如果你的方法没有权限 那么就随都可以进来 直接放行
if (!handlerMethod.hasMethodAnnotation(RequestPermission.class))
return true;
//到这里就说明需要权限
//如果我是Controller 的方法 那我就需要先判断 你是返回路径 还是返回数据
if (handlerMethod.hasMethodAnnotation(ResponseBody.class)) {
//如果你是返回的响应体 那我就返回字符串给你
response.setContentType("application/json;charset=utf-8");
//我通过原生的写 写出去
response.getWriter().write(JSON.toJSONString(new JsonResult(false,"没有权限")));
return false;
}
//走到这里的都没有 ResponseBody 那就都是
RequestPermission permission = handlerMethod.getMethodAnnotation(RequestPermission.class);
// 拿到对应的权限的 表达式 再去权限集合中判断有没有
String expression = permission.expression();
expressions.forEach(System.out::println);
if (!expressions.contains(expression)) {
response.sendRedirect("/permission/nopermission");
return false;
}
}
return true;
}
}
效果展示:
主要的思路和逻辑大概就是这样