目录
demo的地址,自行获取《《------------------------------------------------------------------------------
Spring Boot整合Aop面向切面编程实现权限校验,SpringAop+自定义注解+自定义异常+全局异常捕获,实现权限验证,要求对每个接口都实现单独的权限校验
Spring Boot整合Aop面向切面编程实现权限校验,SpringAop+自定义注解+自定义异常+全局异常捕获,实现权限验证,要求对每个接口都实现单独的权限校验
背景
在日常开发中,为了保证系统稳定性,防止被恶意攻击,我们可以控制用户访问接口的频率,
颜色部分表示窗口大小
在指定时间内,只能允许访问N次,我们将这个指定时间T,看出一个滑动的窗口宽度,Redis的zset的score为滑动窗口,在操作zset的时候,只保留窗口数据,删除其他数据
1.创建切面
javapackage 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 { @Autowired private 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和url String 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的key Long 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.创建自定义注解
javapackage 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.自定义异常类
javapackage 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.全局异常捕获
javapackage 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) @ResponseBody public Result conditionException(ConditionException e){ return Result.fail(e.getCode(),e.getMessage()); } }
5.Controller层
javapackage 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实现的是对同一用户的反复点击进行频率限制,也可用作幂等性校验,具体值需要设置,还需要考虑分布式情况,加上分布式锁