AOP + 自定义注解 教你实现登入校验~

🚀 开篇寄语:相信看这篇文章之前你已经学会了使用拦截器进行登入校验,那么这篇文章教你用另一种方式实现拦截校验,快来看看吧~

目录

📖前言

💡一、思路讲解

💡二、代码实现

1.导入依赖

2.自定义注解

3.声明切面类

🔥总结


📖前言

登入校验的方式有很多种,常见的登入校验方式有拦截器统一拦截和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的原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值