注解和AOP的简单权限认证

背景

在做课程项目的时候,需要使用到权限认证,但是如果在代码中的每个Service层的业务方法中来进行权限的判断未免有点太不优雅

所以这个时候就开始想到,是否能够将鉴权放在注解中或者在拦截器中直接进行权限判断,于是开始找框架

首先找到的是Spring Security,一看使用方法感觉自己头都大了,用起来未免过于复杂

然后找到的是Sa-token,虽然这个框架功能齐全,而且使用方便,但是发现他的Token直接交由框架管理。而我已经做好了Token的生成和解析,如果要引入的话需要在框架下实现,不想做代码的改动。

而且这也只是一个单体课程作业,虽然想将一些功能进行更好的实现,并不需要框架内的绝大多数功能,所以自己编写一个简单的鉴权工具。

前景提要

因为是做一个简单的工单系统,所以并不需要多复杂的设计。

因为已经自己做了一个拦截器进行拦截Token并进行解析,并且把解析出来的 UserDTO 放入了持有类中

持有类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

public static void saveUser(UserDTO userDTO) {
tl.set(userDTO);
}

public static UserDTO getUserDTO() {
return tl.get();
}

public static void removeUser() {
tl.remove();
}
}

Token生成使用jjwt,Token中含有DTO对象,在登录拦截器中解析Token后将其放入持有类中就能保证可以随时获取到DTO对象,并且非常安全。

鉴权Annotation实现

首先引入AOP的依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

开启AOP

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@MapperScan("work.wucheng.workordersystem.mapper")
@EnableAspectJAutoProxy
public class WorkOrderSystemApplication {

public static void main(String[] args) {
SpringApplication.run(WorkOrderSystemApplication.class, args);
}

}

然后创建一个Annotation

1
2
3
4
5
6
7
// 作用在方法中
@Target(ElementType.METHOD)
// 运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthFilter {
int[] value() default USER_TYPE_USER;
}

USER_TYPE_USER是一个常量,因为用户类别不多,所以设计上简单,仅用0123表示,用户数量较多时可以考虑参考Linux的二进制权限表示设计

Aspect切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Aspect
@Component
@Slf4j
public class AuthCheckAspect {

@Around("@annotation(authFilter)")
public Object before(ProceedingJoinPoint joinPoint, AuthFilter authFilter) throws Throwable {
// 打印日志
Signature signature = joinPoint.getSignature();
String className = joinPoint.getTarget().getClass().getSimpleName();
log.info("所执行的类与方法:{} | method:{}",className, signature.getName());

// 获取注解中的值
int[] value = authFilter.value();
Integer type = UserHolder.getUserDTO().getType();

// 按照设计 权限值越小越高,所以只要有用户权限小于等于注解中的权限就执行方法,否则抛出异常
for (int i : value) {
//如果含有此权限或者更高权限,则执行方法
if (type.intValue() <= i){
return joinPoint.proceed();
}
}
throw new RuntimeException("权限不足");
}
}

在Method中使用即可进行鉴权

1
2
3
4
5
@PostMapping("/add")
@AuthFilter(USER_TYPE_ADMIN)
public Result add(@RequestBody User user){
return userService.add(user);
}

权限的设计进行了一波偷懒,并没有区分出权限和用户身份,考虑到只是单体作业,所以尽量避免过于复杂设计,但是记录保留一些比较好的通用方法的思路还是更为重要。