权限管理注解 @AuthCheck
及 AOP
使用方法
在需要校验权限的方法(控制器方法)上,加上 @AuthCheck
注解并指定该方法需要什么角色才能访问。
如果没有权限进行操作,就会抛出异常。
比如:
在方法上使用注解(anyRole)
@AuthCheck(anyRole = {"admin", "user"})
最终在AuthInterceptor
中,会校验,如果当前登录用户是 anyRole 数组中任一角色,即可访问被这个注解注解的方法。
在方法上使用注解(mustRole)
@AuthCheck(mustRole = "admin")
最终在AuthInterceptor
中,会校验,如果当前登录用户是 mustRole 指定的角色,即可访问被这个注解注解的方法。
源码学习
AuthCheck 注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {
/**
* 有任何一个角色
*
* @return
*/
String[] anyRole() default "";
/**
* 必须有某个角色
*
* @return
*/
String mustRole() default "";
}
一篇文章,全面掌握Java自定义注解(Annontation)
注解的定义
注解的定义通过@interface表示,所有的注解会自动继承java.lang.Annotation接口,且不能再继承别的类或是接口。
注解的成员参数只能用public或默认(default) 访问权修饰来进行修饰。
成员参数只能使用八种基本类型(byte、short、char、int、long、float、double、boolean)和String、Enum、Class、annotations等数据类型,及其数组。
获取类方法和字段的注解信息,只能通过Java的反射技术来获取 Annotation 对象。
注解可以没有定义成员,只做标识。
元注解
**@Target**
和**@Retention**
是元注解(注解其他注解的注解)
@Documented – 注解是否将包含在JavaDoc中
@Retention – 注解的生命周期
@Target – 注解用于什么地方
@Inherited – 是否允许子类继承该注解
@Repeatable - 是否可重复注解,jdk1.8引入
注解的生命周期
通过@Retention定义注解的生命周期,格式如下:
@Retention(RetentionPolicy.SOURCE)
其中RetentionPolicy的不同策略对应的生命周期如下:
RetentionPolicy.SOURCE : 仅存在于源代码中,编译阶段会被丢弃,不会包含于class字节码文件中。@Override, @SuppressWarnings都属于这类注解。
RetentionPolicy.CLASS : 默认策略,在class字节码文件中存在,在类加载的时被丢弃,运行时无法获取到。
RetentionPolicy.RUNTIME : 始终不会丢弃,可以使用反射获得该注解的信息。自定义的注解最常用的使用方式。
注解的作用目标
通过@Target定义注解作用的目标,比如作用于类、属性、或方法等,默认可用于任何地方。格式如下:
@Target(ElementType.TYPE)
对应ElementType参数值适用范围如下:
ElementType.TYPE: 类、接口、注解、enum
ElementType.CONSTRUCTOR: 构造函数
ElementType.FIELD: 成员变量、对象、属性、枚举的常量
ElementType.LOCAL_VARIABLE: 局部变量
ElementType.METHOD: 方法
ElementType.PACKAGE: 包
ElementType.PARAMETER: 参数
ElementType.ANNOTATION_TYPE): 注解
ElementType.TYPE_PARAMETER:类型参数,表示这个注解可以用在 Type的声明式前,jdk1.8引入。
ElementType.TYPE_USE:类型的注解,表示这个注解可以用在所有使用Type的地方(如:泛型,类型转换等),jdk1.8引入。
AOP 实现权限管理拦截器
@Aspect
@Component
public class AuthInterceptor {
@Resource
private UserService userService;
/**
* 执行拦截
*
* @param joinPoint
* @param authCheck
* @return
*/
@Around("@annotation(authCheck)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
// 获取注解的 anyRole数组, 将其转换为 List
List<String> anyRole = Arrays.stream(authCheck.anyRole()).filter(StringUtils::isNotBlank).collect(Collectors.toList());
// 获取注解的 mustRole
String mustRole = authCheck.mustRole();
// 获取当前登录用户
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
User user = userService.getLoginUser(request);
// 拥有anyRole中任意权限即通过
if (CollectionUtils.isNotEmpty(anyRole)) {
String userRole = user.getUserRole();
if (!anyRole.contains(userRole)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
}
// 必须有所有权限才通过
if (StringUtils.isNotBlank(mustRole)) {
String userRole = user.getUserRole();
if (!mustRole.equals(userRole)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
}
// 通过权限校验,放行
return joinPoint.proceed();
}
}
数据库 user 表中,有一个 varChar 类型的 userRole 字段。
更完善的权限管理方法,应该使用 RBAC 模型,即数据库要有 权限表、角色表、用户表、角色权限表、用户角色表。而鱼皮这里只是简单使用一个字段表示用户角色。
完美的方案参考:
评论区