使用aop去实现token校验
前言
昨天去面试,被问懵的一个面试题,面试官看了一下简历,轻笑了一声原来你是用拦截器做的token校验啊,那么改用aop你怎么去做校验。我当时脑袋一篇空白。下面就写个小demo
AOP通知
先回顾一下aop的通知,aop通知有五种分别如下
- 前置通知:方法执行前通知
- 后置通知:方法执行后通知
- 环绕通知:方法执行中通知
- 异常抛出通知:方法抛出异常
- 引介通知:类中增加新的方法属性
我们需要校验用户进行拦截oken,那么在执行到这个方法的时候拦截到,就需要用到环绕通知。
导入Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
实际代码
我们先写一个登录类,使用hutool工具类去创建Token,中间加一些验证和设置过期时间
@PostMapping("/login")
public String userLogin(@RequestBody Map<String, String> map) {
// 获取账号和密码
String username = map.get("username");
String password = map.get("password");
// 进行校验
if (StringUtils.isNotBlank(username) && StringUtils.isBlank(password)) {
return "username || password error";
}
if (!username.equals("yefeng") && password.equals("123456")) {
return "username || password error";
}
// 获取现在时间
DateTime now = DateTime.now();
// 过期时间为10分钟
DateTime newTime = now.offsetNew(DateField.MINUTE, 10);
Map<String, Object> payload = new HashMap();
//签发时间
payload.put(JWTPayload.ISSUED_AT, now);
//过期时间
payload.put(JWTPayload.EXPIRES_AT, newTime);
//生效时间
payload.put(JWTPayload.NOT_BEFORE, now);
//载荷
payload.put("username", username);
payload.put("password", password);
// 加密
String key = "yefeng";
String token = JWTUtil.createToken(payload, key.getBytes());
return token;
}
测试接口,发现Token生成成功,接下来我使用AOP去进行验证
首先我们创建一个TokenVerificationAop类, 并设置一个切入点和放行路径
@Aspect
@Component
@Slf4j
public class TokenVerificationAop {
/**
* 登录路径 ->放行
*/
private static final String LOGIN = "login";
/**
* @Pointcut 声明切入点表达式。
* 注意此处扫描的是你的Controller层接口位置
*/
@Pointcut("execution(public * com.yefeng.controller..*..*(..))")
public void pointcut() {
}
}
设置好切入点之后,ProceedingJoinPoint是JoinPoint子接口,获取请求头里面的Authorization,判断请求URI是否包含login,如果包含就执行业务逻辑并且放行
/**
* @Around 环绕通知
* @Around("pointcut()") 可以理解为对这个方法进行环绕通知
* ProceedingJoinPoint 参数 用于环绕通知,
* 使用proceed()方法来执行目标方法,可以理解为 前置通知结束 开始执行使用该注解的方法。
*/
@Around("pointcut()")
public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// assert 是判断然后报异常的, 符合条件继续往下进行
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("Authorization");
if (request.getRequestURI().contains(LOGIN)) {
return joinPoint.proceed();
}
return null;
}
如果不是登录接口那么我们需要进行Token校验具体代码如下
@Around("pointcut()")
public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
.....
if (StringUtils.isNotBlank(token)) {
try {
String key = "yefeng";
JWT jwt = JWTUtil.parseToken(token);
boolean verify = jwt.setKey(key.getBytes()).verify();
System.out.println(verify);
boolean verifyTime = jwt.validate(0);
System.out.println(verifyTime);
} catch (Exception e) {
// 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现 401
log.info("AuthorizeFilter 解析jwt令牌出错", e);
//此处可以返回自定义 Result结果对象 转成Json格式
throw new Exception("解析jwt令牌出错");
}
} else {
log.info("AuthorizeFilter 登录令牌不存在");
throw new Exception("登录令牌不存在");
}
//执行业务逻辑,放行
return joinPoint.proceed();
}
完整代码如下
/**
* 使用aop进行token校验
* @author yefeng
*/
@Aspect
@Component
@Slf4j
public class TokenVerificationAop {
/**
* 登录路径 ->放行
*/
private static final String LOGIN = "login";
/**
* @Pointcut 声明切入点表达式。
* 注意此处扫描的是你的Controller层接口位置
*/
@Pointcut("execution(public * com.yefeng.controller..*..*(..))")
public void pointcut() {
}
/**
* @Around 环绕通知
* @Around("pointcut()") 可以理解为对这个方法进行环绕通知
* ProceedingJoinPoint 参数 用于环绕通知,
* 使用proceed()方法来执行目标方法,可以理解为 前置通知结束 开始执行使用该注解的方法。
*/
@Around("pointcut()")
public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// assert 是判断然后报异常的, 符合条件继续往下进行
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
String token = request.getHeader("Authorization");
if (request.getRequestURI().contains(LOGIN)) {
//执行业务逻辑,放行
return joinPoint.proceed();
}
if (StringUtils.isNotBlank(token)) {
try {
String key = "yefeng";
JWT jwt = JWTUtil.parseToken(token);
boolean verify = jwt.setKey(key.getBytes()).verify();
System.out.println(verify);
boolean verifyTime = jwt.validate(0);
System.out.println(verifyTime);
if (!verify) {
return "token 有误";
}
} catch (Exception e) {
// 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现 401
log.info("AuthorizeFilter 解析jwt令牌出错", e);
//此处可以返回自定义 Result结果对象 转成Json格式
throw new Exception("解析jwt令牌出错");
}
} else {
log.info("AuthorizeFilter 登录令牌不存在");
throw new Exception("登录令牌不存在");
}
//执行业务逻辑,放行
return joinPoint.proceed();
}
}
测试
写一个测试接口
@GetMapping("/home")
public String home() {
return "token";
}
我们随便写一个Token,发送请求,测试结果如下
那么我们弄一个有效Token,测试结果如下,说明我的aop进行token校验成功