权限管理-AOP
近期回顾项目时决定将AOP权限管理这块整理一下:
什么是AOP/为什么要使用AOP?
AOP是软件设计领域中的面向切面编程 ,在 OOP 中, 我们以类(class)作为我们的基本单元, 而 AOP 中的基本单元是 Aspect(切面) **
够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码**,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
AOP主要结构
简单地去理解,其实AOP要做三类事:
- Pointcut 在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。
- Advice 在什么时候切入,是业务代码执行前还是执行后。
- Advice 切入后做什么事,比如做权限校验、日志记录等。
可以看到处理时机是由 通知(Advice)类型来决定,而具体处理逻辑是写在了Aspect切面类中
元注解
这里使用元注解主要是为了进行各模块权限分配**@Permission**
@Target注解用于定义注解的使用位置
@Retention注解用于指明修饰的注解的生存周期,即会保留到哪个阶段
@Documented 指明修饰的注解
代码实现
1、首先引入依赖
<!--aop切面的使用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--切面注解支持-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
2、定义aspect切面类**@Aspect**
创建一个AOP切面类,只要在类上加个@Aspect注解即可。@Aspect注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component注解将该类交给 Spring 来管理。在这个类里实现advice:
/**
* 定义切点(切入位置)
* Pointcut后定义哪些bean哪些方法是需要处理的目标
* *.* 代表controller包下所有类和所有方法
* execution(修饰符表达式? 返回值表达式 类路径表达式?方法名表达式(参数表达式)异常表达式?)
* 说明:包名部分的..表示当前包及其所有子包.后面带有?的表示为可选项.参数部分的..表示零个或多个任意参数
*/
@Pointcut("execution(public * cn.stu.scu.controller.*.*(..))")
public void privilege() {
// System.out.println("==>切入");//不执行
}
/**
* 下面是核心业务
* 权限环绕通知
* joinPoint连接点
*/
@ResponseBody
@Around("privilege()") //value=privilege(),对应切点方法
// @Around("execution(public * com.example.demo.controller.*.*(..))") //或者直接写入切入位置
// @Around("execution(* com.example.demo.controller.*.*(..))") //修饰符默认就是public
public Object isAccessMethod(ProceedingJoinPoint joinPoint) throws Throwable {
//增强处理
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();//获取访问的目标方法签名
Method targetMethod = methodSignature.getMethod(); //目标方法
//如果该方法上没有权限注解,直接调用目标方法
if (!targetMethod.isAnnotationPresent(Permission.class)) {
return joinPoint.proceed();//放行,调用目标组件方法
} else {
// 获取自定义注解对象
Permission permission = targetMethod.getAnnotation(Permission.class);
// 根据对象获取注解值
int authorities = permission.authorities();
Object[] args = joinPoint.getArgs();//获取目标方法的参数列表(用户名)
if (args == null) {
throw new RejectedExecutionException("参数错误");
}
String token = ((HttpServletRequest) args[0]).getHeader("X-Token");
String currentUser = token;
// 权限比对
int nowPermission = userService.getPermission(currentUser);
if (nowPermission >= 3) {
throw new SecurityException("请重新登录");
// return "sorry,您不是管理员";
} else if (nowPermission > authorities) {
throw new RejectedExecutionException("权限错误");
// return "sorry,您不是管理员";
} else {
// logger.info("您是管理员");
return joinPoint.proceed();//放行
}
}
}
3、自定义注解@Permission
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Permission {
// String authorities() default "ADMIN";
int authorities() default 3; //0开发者,1管理员,2普通用户
}
这里permission 在user数据表中有事先定义
下面梳理一下涉及到的各个知识点
AOP底层原理
拦截器与AOP区别
多个AOP执行顺序
@Permission-自定义注解
创建注解类,注意这是一个**@interface,在Kind出选择Annotation**
@Target({ElementType.TYPE}) 注解
ElementType 这个枚举类型的常量提供了一个简单的分类:注解可能出现在Java程序中的语法位置(这些常量与元注解类型(@Target)一起指定在何处写入注解的合法位置)
@Retention({RetentionPolicy.Runtime}) 注解
RetentionPolicy这个枚举类型的常量描述保留注解的各种策略,它们与元注解(@Retention)一起指定注释要保留多长时间
@Pointcut 注解,
用来定义一个切点,即上文中所关注的某件事情的入口,切入点定义了事件触发时机和定义需要拦截的东西。execution表达式:
以 execution(* * com.mutest.controller….(…))) 表达式为例:
- 第一个 * 号的位置:表示返回值类型,* 表示所有类型。
- 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。
- 第二个 * 号的位置:表示类名,* 表示所有类。
- (…):这个星号表示方法名, 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
当定义一个Around增强处理方法时,该方法的第一个形参必须是ProceedingJoinPoint类型(至少一个形参)。