【Springboot】基于AOP和自定义注解实现权限校验

前言:

        (1)假设现在有一个项目,用户只需登录就可以访问所有接口。基于这种场景,我们一般的权限鉴权逻辑是:用户登录后生成一个随机token保存到redis并返回给用户端,用户随后的每次请求都会携带这个token。服务器配置拦截器拦截用户请求,查看请求是否携带token并且查询token是否有效,若请求没有携带token或者token过期,直接拒绝请求,若token有效则刷新token的过期时间,放行请求。

        (2)随着项目业务升级,用户可以充值办理会员,项目中的一些接口现在只有会员才能访问,而拦截器只能拦截未登录用户的请求,此时就需要我们在需要会员权限的接口加一些权限判断逻辑(需要改动代码)。有没有一种方法在不改动代码的情况下,对请求进行更进一步的权限校验呢?这就需要我们今天的”主角“AOP了。

思路:

        不再通过拦截的方式实现接口鉴权,而是在需要鉴权的接口上添加自定义注解,通过Spring AOP对标注了自定义注解的接口进行逻辑增强,在执行真正的业务处理逻辑前校验token是否存在且有效、用户是否具有相应权限,若校验通过再执行真正的业务处理逻辑。

如何自定义注解?

        (1)注解关键字:@interface。

        (2)@Target:注解的作用范围。该注解有一个value属性,其类型是ElementType数组,你可以声明这个注解作用在多种类型上,如下所示。

//FIELD:作用在字段上,METHOD:作用在方法上,TYPE:作用在类上
@Target(value = {ElementType.FIELD,ElementType.METHOD,ElementType.ANNOTATION_TYPE})

        (3)@Retention:注解的生命周期。

        (4)value:String类型,表示该接口需要的权限类型,有普通用户会员这两种权限类型。

//注解需作用在方法上
@Target(ElementType.METHOD)
//注解会保留到程序运行期
@Retention(RetentionPolicy.RUNTIME)
public @interface Authority {


    //这是访问接口时,用户需要拥有的权限
    //用户有两种角色:普通用户、会员
    String value() default "";
}

权限校验流程

        (1)在pom文件中引入aop依赖:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        (2)定义切面类:

//声明一个类为切面类
@Aspect
//将该类注入到IOC容器中,交给Spring管理
@Component
public class AuthorityAspectConfig {


    @Resource
    private StringRedisTemplate template;//spring封装的redis客户端

    @Resource
    ObjectMapper mapper;//json转换工具

    /**
     * 声明一个切入点
     * 切入点为:所有标注了Authority注解的方法
     */
    @Pointcut("@annotation(com.hammajang.springbootdemo.config.Aspect.Authority)")
    public void authority(){

    }

    /**
     * 具体验证业务数据
     */
    @Around("authority()")
    public void doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        //1.判断请求是否携带token
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            throw new RuntimeException("用户未登录,请先登录");
        }

        //2.校验token是否过期
        //2.1通过token从redis中取出用户信息
        String userStr = template.opsForValue().get(token);
        //2.2用户为null,说明是无效token
        if (userStr == null){
            throw new RuntimeException("非法请求,token无效");
        }
        User user = mapper.readValue(userStr, User.class);

        //2.3不为null,则token未过期,刷新token...
        template.opsForValue().set(token,userStr,30, TimeUnit.MINUTES);


        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        Authority authority = method.getAnnotation(Authority.class);
        //获取注解的值
        String value = authority.value();

        //如果需要会员权限,判断用户是否是会员
        if(value.equals("1") && user.getRole() != 1)
        throw new RuntimeException("用户没有足够权限!");


        // 执行真正的业务处理逻辑
        Object result = proceedingJoinPoint.proceed();

        long end = System.currentTimeMillis();
        
        //打印接口耗时
        System.out.println(proceedingJoinPoint.getSignature().getName() + "接口执行总耗时:" + (end - start) +"ms");
    }
}

        (3)标注需要权限权限的接口:

@RestController
@RequestMapping("/coupon")
public class CouponController {

    
    //普通用户:0
    //会员:1
    @Authority("1")
    @GetMapping("/test")
    public String checkCoupon(){
        System.out.println("需要用户是会员的优惠券接口...");
        return "success";
    }
}

测试注解是否生效

        使用postman工具测试

        (0)提前通过测试类在redis中保存一个用户的信息。

@SpringBootTest
class SpringbootDemoApplicationTests {

    @Resource
    StringRedisTemplate template;

    @Test
    void test() throws JsonProcessingException {
        User user = new User();
        user.setId(3);
        user.setRole(0);

        ObjectMapper mapper = new ObjectMapper();
        //将对象转成JSON格式
        String jsonStr = mapper.writeValueAsString(user);
        
        //去除UUID自带的横杠
        char[] chars = UUID.randomUUID().toString().toCharArray();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < chars.length; i++)
            if(chars[i] != '-')
                sb.append(chars[i]);
        String token = sb.toString();
        //将随机生成的token写入redis,并设置过期时间为30min
        template.opsForValue().set(token,jsonStr,30, TimeUnit.MINUTES);
    }

}

        token及用户信息成功保存在redis中:

        (1)请求不携带token的情况:

        控制台打印:

        (2)请求携带token,但是token无效的情况:

        控制台打印:

        (3)请求携带有效token,但用户权限不够的情况:

        控制台打印:

        (4)修改用户权限为1,再次访问该接口:

        控制台打印:

经过测试自定义注解有效,至此就完成了基于自定义注解+AOP的形式实现接口鉴权的目的,如果有什么错误的地方,请在评论区指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值