在编写接口api时,基于数据安全的考虑,我们通常会在接口进行权限校验,有权限的才可以查询数据,没有权限我们可以抛出异常“没有权限,禁止访问“。通常,我们实现是这样的:
@ApiOperation("根据id查询用户信息")
@GetMapping("/data/{id}")
public BaseResponse<String> getData(@PathVariable String id) {
// 当前接口需要的权限
String permission = "ROLE_ADMIN";
// 校验当前用户有没有这个权限
validateUserPermission(permission);
String data = getDataById(id);
return BaseResponse.success(data);
}
咋一看,接口代码不多,逻辑也不是很复杂,这样写也没有什么问题。但是你想一下,当有几十上百个接口都要进行鉴权时,那么这些代码加起来,就不少了;而且一旦鉴权的逻辑发生改变,那么那些使用过的地方都需要做修改,这就不能让人接受了。
实际上,我们会发现,这个接口分为两部分,一部分是鉴权,一部分是获取数据,只有鉴权通过,才可以获取数据。这种场景就很适合使用AOP来织入鉴权的功能。下面我们来展示优化后的代码:
首先,我们定义一个(权限)注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Demo {
String[] value() default {};
}
配置切面:
@Aspect
@Component
public class DemoLoggingAspect {
@Before(value = "@annotation(demo)")
public void beforeMethod(Demo demo) {
// 接口访问需要的权限
ArrayList<String> list = Lists.newArrayList(demo.value());
validateUserPermission(list);
System.out.println("method start time " + System.currentTimeMillis());
}
private void validateUserPermission(List<String> permissions) {
// 不需要权限直接放行
if (CollectionUtils.isEmpty(permissions)) {
System.out.println("不需要权限,直接放行");
return;
}
// 获取用户权限
List<String> userPermissions = getUserPermission();
// 校验权限
if (!userPermissions.containsAll(permissions)) {
throw new RuntimeException("没有权限,禁止访问");
}
}
private List<String> getUserPermission() {
System.out.println("获取用户权限");
return Lists.newArrayList("ROLE_ADMIN");
}
}
然后需要鉴权的接口加上Demo这个注解,就可以实现鉴权了
@Demo(value = {"ROLE_ADMIN"})
@ApiOperation("根据id查询用户信息")
@GetMapping("/data/{id}")
public BaseResponse<String> getData(@PathVariable String id) {
String data = getDataById(id);
return BaseResponse.success(data);
}
当然了,AOP里面的其他四种通知类型(返回通知 (@AfterReturning) 、异常通知 (@AfterThrowing) 、 后置通知 (@After) 、环绕通知 (@Around)),感兴趣的可以自己去了解。