目录
1.创建切面
2.创建自定义注解
3.自定义异常类
4.全局异常捕获
5.Controller层
demo的地址,自行获取《《——————————————————————————
Spring Boot整合Aop面向切面编程实现权限校验,SpringAop+自定义注解+自定义异常+全局异常捕获,实现权限验证,要求对每个接口都实现单独的权限校验
Spring Boot整合Aop面向切面编程实现权限校验,SpringAop+自定义注解+自定义异常+全局异常捕获,实现权限验证,要求对每个接口都实现单独的权限校验
背景
在日常开发中,为了保证系统稳定性,防止被恶意攻击,我们可以控制用户访问接口的频率,
颜色部分表示窗口大小
在指定时间内,只能允许访问N次,我们将这个指定时间T,看出一个滑动的窗口宽度,Redis的zset的score为滑动窗口,在操作zset的时候,只保留窗口数据,删除其他数据
1.创建切面
package com.example.aspect;import com.example.annotation.RequestLimiting; import com.example.exception.ConditionException; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.operators.arithmetic.Concat; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest; import java.util.concurrent.TimeUnit;@Aspect @Slf4j @Component public class ApiLimitedRoleAspect {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Pointcut("@annotation(com.example.annotation.RequestLimiting)")public void poincut() {}@Around("poincut() && @annotation(requestLimiting)")public Object doAround(ProceedingJoinPoint proceedingJoinPoint, RequestLimiting requestLimiting) throws Throwable {//获取注解参数long period = requestLimiting.period();long count = requestLimiting.count();ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();//获取ip和urlString ip = request.getRemoteAddr();String uri = request.getRequestURI();//拼接redis的key值String key = "reqLimiting" + uri + ip;ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();//获取当前时间long curTime = System.currentTimeMillis();//添加当前时间zSetOperations.add(key, String.valueOf(curTime), curTime);//设置过期时间stringRedisTemplate.expire(key, period, TimeUnit.SECONDS);//删除窗口之外的值//这个a指的是实际窗口的范围边界,比如,我从10:00开始访问,//每秒访问1次,在11:12时,记录里就会有62条,此时的a表示10:12,//最后会删除掉10:00-10:12的所有key,留下的都10:12-11:12的keyLong a = curTime - period * 1000;log.error(String.valueOf(a));zSetOperations.removeRangeByScore(key, 0, a);//设置访问次数Long acount = zSetOperations.zCard(key);if (acount > count) {log.error("接口拦截:{}请求超过限制频率{}次/{}s,ip为{}", uri, count, period, ip);throw new ConditionException("10004", "接口访问受限,请稍后");}return proceedingJoinPoint.proceed();}}
2.创建自定义注解
package com.example.annotation;import java.lang.annotation.*;/** *<p> </p> *自定义注解* 接口访问频率 *@author panwu *@descripion *@date 2024/3/24 13:23 */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequestLimiting {//窗口宽度long period() default 60;//允许访问次数long count() default 5; }
3.自定义异常类
package com.example.exception;public class ConditionException extends RuntimeException {private static final long serialVersionUID = 1L;private String code;// public ConditionException(ConditionExceptionEnum conditionExceptionEnum){ // super(conditionExceptionEnum.getDesc()); // this.code = conditionExceptionEnum.getCode(); // }public ConditionException(String code, String name) {super(name);this.code = code;}public ConditionException(String name){super(name);code="500";}public String getCode() {return code;}public void setCode(String code) {this.code = code;}}
4.全局异常捕获
package com.example.exception;import com.example.dto.Result; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) public class ComonGlobalExceptionHandler {@ExceptionHandler(value = ConditionException.class)@ResponseBodypublic Result conditionException(ConditionException e){return Result.fail(e.getCode(),e.getMessage());} }
5.Controller层
package com.example.controller;import com.example.annotation.RequestLimiting; import com.example.dto.Result; import lombok.extern.log4j.Log4j; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@Slf4j @RestController @RequestMapping("/user") public class UserController {@GetMapping()@RequestLimiting(count = 3)public Result get(){log.debug("访问成功");return Result.ok("访问成功");} }
要知道的是,该Demo实现的是对同一用户的反复点击进行频率限制,也可用作幂等性校验,具体值需要设置,还需要考虑分布式情况,加上分布式锁