灵感来源:ruoyi-plus中推荐的Sa-Token权限认证框架.
需求:对于不用角色用户我们需要对其接口权限进行控制,首先在体验上我们需要在前端控制那些没有权限的按钮不显示
前端实现
我们的前端是使用vue2,可以通过自定义标签v-permission控制是否展示该按钮,实现过程,用户登录的时候后台提供该用户所有的权限标识符,前端将这些数据缓存起来,我是存在了sessionStorage中,代码如下:
/**
* 自定义指令,按钮是否有权限
*/
Vue.directive("permission", {
inserted(el, binding) {
el.style.display = "none";
let permission = binding.value
let permissionStr = window.sessionStorage.getItem("permission");
if ((!permissionStr)) {
removeToken()
removeInformation()
router.push("/login");
return;
}
let hasPermission = permissionStr.indexOf(permission) !== -1;
if (permissionStr === '*' || hasPermission) {
el.style.display = "inline-block";
} else isPermission(permission).then(hasPermission => {
if (hasPermission) {
el.style.display = "inline-block";
}
})
}
})
isPermission方法是后台请求,因为存在一种情况,用户发现自己没有权限,去问了上级领导,上级领导发现是没有给他分配权限,于是给他分配了权限,但是我们的权限标识是缓存起来的,于是我们提供了请求后台的接口.
使用:
<el-button v-permission="'crm:repair:delete'" type="danger" size="mini"
slot="reference">
删除
</el-button>
后端实现:为了接口安全,后端也必须对接口权限进行控制
1.自定义注解@ApiCheckPermission
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ApiCheckPermission {
// 权限字符,支持多个
String[] value() default {};
// 权限字符连接类型 and全部包括, or一个符合即可
SysMode permissionMode() default SysMode.OR;
// 是否需要管理员权限("admin")
String type() default "";
String[] orRole() default {};
}
2.处理鉴权逻辑
@Component
@Aspect
@EnableAsync
@Slf4j
public class AspectCheckPermission {
// 用注解的方式定义切点
@Pointcut("@annotation(com.gegeda.sp.log.aop.ApiCheckPermission)")
public void point() {
}
// 用前置通知的方式鉴权
@Before("point()")
public void before(JoinPoint jp) throws GraceException {
// 获取操作用户
LoginUser loginUser = LoginUtils.getLoginUser();
// 需要鉴权的方法必须存在操作用户
if (loginUser == null) {
throw new InjectException(ResponseStatusEnum.UN_LOGIN);
}
// bug用户
if (loginUser.getId() == 1) {
return;
}
// 获取需要鉴权的方法注解
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
ApiCheckPermission annotation = method.getAnnotation(ApiCheckPermission.class);
// 获取注解参数
List<String> permissionList = Arrays.asList(annotation.value());
SysMode permissionMode = annotation.permissionMode();
Set<String> permissions = loginUser.getPermissions();
// 缓存中不存在用户权限字符数据需要用户重新登录刷新
if (permissions == null) {
throw new InjectException("权限校验失败, 您可以尝试退出并重新登录...");
}
// 判断是否需要管理员权限
String type = annotation.type();
if ("admin".equals(type)) {
Integer integer = loginUser.getIsAdmin();
if (integer != 1) {
throw new NotPermissionException("您不是管理员, 操作权限不足...").setCode(503);
}
}
// 校验权限字符
boolean checkPermissionRes = this.checkPermission(permissionMode, permissions, permissionList);
// 权限字符校验不通过抛出权限不足异常
if (!checkPermissionRes) {
throw new NotPermissionException();
}
}
private boolean checkPermission(SysMode mode, Set<String> permissions, List<String> permissionList) {
switch (mode) {
case AND:
return permissions.containsAll(permissionList);
case OR:
for (String permission : permissionList) {
if (permissions.contains(permission)) {
return true;
}
}
}
return false;
}
注意:LoginUtils是自定义工具类,http请求中有token,可以获取Redis中的用户缓存数据信息,从而避免数据库查询.
3.自定义异常类
public class NotPermissionException extends RuntimeException {
private static final long serialVersionUID = 6806129545290130132L;
private int code = ResponseStatusEnum.NO_PERMISSION.status();
public NotPermissionException() {
}
public NotPermissionException(int code) {
this.code = code;
}
public NotPermissionException(String message) {
super(message);
}
public NotPermissionException(int code, String message) {
super(message);
this.code = code;
}
public NotPermissionException(Throwable cause) {
super(cause);
}
public NotPermissionException(String message, Throwable cause) {
super(message, cause);
}
public int getCode() {
return this.code;
}
public NotPermissionException setCode(int code) {
this.code = code;
return this;
}
public static void throwBy(boolean flag, String message, int code) {
if (flag) {
throw new NotPermissionException(message);
}
}
public static void throwByNull(Object value, String message, int code) {
if (ObjectUtil.isEmpty(value)) {
throw (new NotPermissionException(message)).setCode(code);
}
}
4.注册异常拦截处理
/**
* 权限不足异常
*/
@ExceptionHandler(value = NotPermissionException.class)
public Ret<Void> NotPermissionExceptionHandler(NotPermissionException e) {
return Ret.fail(ResponseStatusEnum.NO_PERMISSION.status(), ResponseStatusEnum.NO_PERMISSION.msg());
}
5.使用
/**
* 补录订单导入
*/
@ApiCheckPermission("crm:repair:import")
@PostMapping("/importOrderRepair")
public Ret<ImportVO> importOrderRepair(
@RequestBody List<OrderRepairExcel> data) {
LoginUser loginUser = LoginUtils.getLoginUser();
if (loginUser == null) {
return Ret.fail("登录状态异常");
}
ImportVO importVO = orderRepairBiz.importOrderRepair(data, loginUser);
return Ret.ok(importVO);
}