🚀 开篇寄语:相信看这篇文章之前你已经学会了使用拦截器进行登入校验,那么这篇文章教你用另一种方式实现拦截校验,快来看看吧~
目录
📖前言
登入校验的方式有很多种,常见的登入校验方式有拦截器统一拦截和AOP代理。在现在常见的微服务架构中,用拦截器实现的登入校验需要在网关中自定义过滤器配置,比较麻烦。而AOP+自定义注解方式就很方便,只需要写一个切面类再加上切点表达式匹配即可。本篇文章主要教会你如何使用AOP+自定义注解的方式实现登入校验。
💡一、思路讲解
我们用自定义注解方式来设置切入点(当然用切面表达式也可以进行,但是利用自定义注解更加方便而且操作粒度跟小)。之后我们定义切面类并选择选择合适的通知(我这里选用的是环绕通知的方式),最后我们在切面类中实现我们的校验逻辑即可。
💡二、代码实现
1.导入依赖
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.6</version>
</dependency>
2.自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD}) // 标签:代表注解的作用类型(类,方法,属性上等等)
@Retention(RetentionPolicy.RUNTIME) // 代表代码运行时生效
public @interface YtLogin {
/**
* 注解声明属性:类型 + 名字() + default + 值
*/
boolean request() default true;
}
注意,因为我们这里的注解仅仅是起到标记的作用,故里面只有一个参数。该参数的作用是设置是否是需要进行校验,如果使用注解时将该参数设置为false,那么我们就只进行登入续期(前提是前端传入token,并且token正确)。
3.声明切面类
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
// 切面类
@Aspect
@Component
@Slf4j
public class YtLoginAspect {
@Autowired
private RedisTemplate redisTemplate;
// 环绕通知
@Around("@annotation(ytLogin)")
public Object login(ProceedingJoinPoint joinPoint, YtLogin ytLogin) throws Throwable {
// 获取request对象
// 获取请求对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 转化为ServletRequestAttributes
ServletRequestAttributes sra = (ServletRequestAttributes) requestAttributes;
// 获取到HttpServletRequest 对象
HttpServletRequest request = sra.getRequest();
// 1 从请求头中获取token
String token = request.getHeader(RedisConstant.TOKEN);
// 需要校验时才校验
if (ytLogin.request()){
//判断token
if (StringUtils.isEmpty(token)) {
//前端根据返回值208,是否跳转登录页面
log.info("token为空,禁止访问...");
throw new RunTimeException(ResultCodeEnum.LOGIN_AUTH);
}
//根据token查询redis
UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
if (userInfo == null) {
log.info("token不存在,禁止访问...");
throw new RunTimeException(ResultCodeEnum.LOGIN_AUTH);
}
}
if (!StringUtils.isEmpty(token)) {
UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
if (userInfo != null) {
AuthContextHolder.setUserId(userInfo.getId());
}
}
log.info("登入校验通过...");
return joinPoint.proceed();
}
}
在这个切面类中,大家需要注意上面获取token的部分。因为在切面类中我们无法直接获取HttpServletRequest对象,故而我们无法直接获取请求头中的token。所以,我通过上述方式进行获取,下面我为大家讲解这样获取HttpServletRequest对象的原理(了解)。
首先分析 RequestContextHolder这个类,里面有两个ThreadLocal 保存当前线程下的request
public abstract class RequestContextHolder {
// 得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
//可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
}
再看getRequestAttributes() 方法,相当于直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request.
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = (RequestAttributes)requestAttributesHolder.get();
if (attributes == null) {
attributes = (RequestAttributes)inheritableRequestAttributesHolder.get();
}
return attributes;
}
总之,上面的获取方式属于固定流程,大家死记即可,不必太纠结于原理。
再说里面的逻辑部分,因为我是用Redis存储的token和用户信息,所以我是这样写的。大概的判断流程基本都是这样,大家可根据自己的业务逻辑自行修改~
🔥总结
AOP+自定义注解的登入校验方式再微服务架构中相较于拦截器校验,它实现简单利于理解,不需要你再去学习Gateway的原理。