按钮基本的权限校验实现
目录
简述
哈哈哈哈哈哈,我答应会经常更新的。很好,再一次做不到,打脸!来来来写写博客吹吹牛逼来了!
是这样的目前很多业务系统都希望系统的权限是可以动态可调整的,而且还希望系统的权限可以精确到按钮级别的。通常会这样做先做个按钮和菜单的功能配置表,去控制前端按钮的显示与否。然后再做一个接口管理表,管理所有接口在某个角色当中是否有权限调用。而且我们需要通过前端页面去配置这些接口和按钮。
今天就说一下我是怎么去实现,不需要去配置有什么接口,有什么按钮的动态权限,同时我也会基于AOP去实现权限认证。
核心类
一、Action 类
主要是记录系统里面有多少个操作,每个接口就算一个操作,我们将功能按钮和对应的接口抽象成一个Action 每一个Action都需要定义一个全局唯一的ActionId,Action还关联着对应的菜单 menuId(主要生成权限树)。
@Entity
@Table(name = "T_ACTION")
public class Action {
public static final int STATUS_ENABLE = 1;
public static final int STATUS_DISABLE = 0;
/**
* 行为ID 全局唯一 英文标识
*/
@Id
@Column(nullable = false)
private String actionId;
/**
* 行为名称 主要用作显示
*/
@Column(nullable = false)
private String actionName;
/**
* 行为对应的接口URI
*/
@Column(nullable = false, length = 256)
private String actionUri;
/**
* 行为所属的菜单ID
*/
@Column(nullable = false)
private String menuId;
@Column(nullable = false, length = 6)
private int status;
}
二、Menu 类
菜单对象,主要用于是否需要显示前端菜单项的。判断可以通过菜单以及子孙菜单是否有相应的action权限,如果当前角色 一个action的权限都没有那就直接将菜单隐藏,这样在返回给前端的权限树当中,就不会有相应的菜单。
Entity
@Table(name = "T_MENU")
public class Menu {
public static final int STATUS_ENABLE = 1;
public static final int STATUS_DISABLE = 0;
public Menu() {
}
public Menu(String menuId, String menuName, String parentMenuId) {
this.menuId = menuId;
this.menuName = menuName;
this.parentMenuId = parentMenuId;
this.status = STATUS_ENABLE;
}
/**
* 菜单ID 全局唯一标识
*/
@Id
@Column(nullable = false)
private String menuId;
/**
* 菜单名称 用于显示
*/
@Column(nullable = false)
private String menuName;
/**
* 父菜单ID 如果没有父级菜单ID 即为顶级菜单
*/
private String parentMenuId;
/**
* 权限状态如果为0则功能已经不存在 或者 已经被取消权限判断
*/
@Column(nullable = false, length = 6)
private int status;
}
三、Role 类
Role 主要是配置和管理有那些求权限,同时就是关联着相应的管理员用户。
@Entity
@Table(name = "T_ROLE")
public class Role {
public static final int STATUS_DISABLE = 0;
public static final int STATUS_ENABLE = 1;
@Id
@GeneratedValue
@Column(nullable = false)
private Long roleId;
/**
* 角色
*/
@Column(nullable = false)
private String name;
/**
* 角色描述
*/
@Column(nullable = false,length = 300)
private String description;
/**
* 角色状态
*/
@Column(nullable = false)
private int status;
/**
* 角色所对应的权限
*/
@ManyToMany(targetEntity = Action.class)
@JoinTable(name = "T_ACTION_ROLE", joinColumns = {@JoinColumn(name = "ROLE_ID")}, inverseJoinColumns = {@JoinColumn(name = "ACTION_ID")})
private Set<Action> actions;
@OneToMany(mappedBy = "role")
private Set<AdminUser> adminUsers;
/**
* 创建角色的时间
*/
@Column(nullable = false)
private Date createTime;
}
分析
目前我们要做到动态权限要做到几个地方:
- 做到接口即Action,定义了接口就等于定义了相关的Action功能,之后我们会通过这个Action去控制权限;
- 需要配置菜单,动态关联上对应的Action;
- 每次启动服务,可以检查整理出最新的action列表(与你的接口变更一致)和 menu列表;
- 用户登录的时候,将用户对应角色拥有的菜单和对应的action 组织成一个权限树,返回给前端渲染菜单和按钮;
- 用户请求接口时候,将会通过AOP读取接口定义的ActionId,通过用户对应关系获得该用户的角色是否有权限访问该接口;
- 提供接口配置相应的角色权限;
权限配置定义和读取
一、Action配置
我们会定义一个annotation,通过annotation配置到对应的接口上,用作actionId定义和AOP校验的切入点。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
String actionId();
String menuId();
/**
* 功能名称
* @return
*/
String name();
}
另外还有一个是不检查权限,只是需要当前用户一定是已经登录并且可以获得其用户信息。我将会定义另外一个annotation进行判断
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorization {
}
定义完成这两个annotation之后,我将使用这两个annotation定义在ResController对应的方法当中。使用方法如下:
@RestController
@RequestMapping("/decorate")
public class DecorateController {
@Autowired
private IDecorateService decorateService;
@GetMapping("/view")
@Permission(actionId = "decorateManage_viewBtn", menuId = "decorateManage", name = "查看小程序装修")
public ResponseBasic<DecorateRsp> view() {
return ResponseBasic.ok(decorateService.view());
}
}
可以看到我在/view 这个接口上面,定义了一个名称为 查看小程序装修 actionId 为 decorateManage_viewBtn 属于 decorateManage 菜单的权限,这个权限将会是全局唯一的。同时我们这个actionId 在用户登录的时候用户拥有什么权限actionId 会全部给前端渲染页面。
二、菜单配置
我们需要定义个菜单配置的类,其实只是配置一个bean将所有配置的菜单保存起来
/**
* Created by TONY YAN
*/
public class PermissionConfig {
private Set<Menu> menus = new HashSet<>();
public Set<Menu> getMenus() {
return this.menus;
}
public PermissionConfig addMenu(Menu menu) {
menus.add(menu);
return this;
}
}
然后就开始配置菜单,在其中一个配置类里面配置定义好的PermissionConfig,定义好各个菜单的名称和menuId,还有就是就是父级的menuId
@Bean
public PermissionConfig menuDefinition() {
PermissionConfig permissionConfig = new PermissionConfig();
permissionConfig.addMenu(new Menu("staffManageIndex", "员工管理", null));
permissionConfig.addMenu(new Menu("roleManage", "角色管理", "staffManageIndex"));
permissionConfig.addMenu(new Menu("staffManage", "员工管理", "staffManageIndex"));
permissionConfig.addMenu(new Menu("storeManage", "门店管理", "staffManageIndex"));
permissionConfig.addMenu(new Menu("orderManageIndex", "订单管理", null));
permissionConfig.addMenu(new Menu("orderSurvey", "订单概况", "orderManageIndex"));
permissionConfig.addMenu(new Menu("orderManage", "所有订单", "orderManageIndex"));
permissionConfig.addMenu(new Menu("goodsManage", "商品管理", null));
permissionConfig.addMenu(new Menu("memberManage", "会员管理", null));
permissionConfig.addMenu(new Menu("decorateManage", "小程序装修", null));
permissionConfig.addMenu(new Menu("cardManage", "礼品卡管理", null));
return permissionConfig;
}
三、配置读取生成权限表
OK,那我们还需要提供一个借口给前端用户,选择某个角色应该拥有什么权限。所以我们需要每次启动SpringBoot应用的时候,将所有其本身定义的所有权限action给记录起来。需要完成这一步的思路是这样的,我们需要在SpringBoot启动后,然后扫描Spring容器里面所有的Bean找到对应的@Permission的配置。上代码:
1、我们需要实现initializingBean接口,在afterPropertiesSet 方法里面获得Spring的context,然后通过获得所有的RestController类的Bean。
/**
* Created by TONY YAN
*/
@Component
public class PermissionInitializing implements InitializingBean {
@Aut