spring boot 通过AOP防止API重复请求

Posted 21-gram

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring boot 通过AOP防止API重复请求相关的知识,希望对你有一定的参考价值。

实现思路

基于Spring Boot 2.x

自定义注解,用来标记是哪些API是需要监控是否重复请求

通过Spring AOP来切入到Controller层,进行监控

检验重复请求的Key:Token + ServletPath + SHA1RequestParas

  1. Token:用户登录时,生成的Token
  2. ServletPath:请求的Path
  3. SHA1RequestParas:将请求参数使用SHA-1散列算法加密

使用以上三个参数拼接的Key作为去判断是否重复请求

使用Redis存储Key,而且redis的特性,key可以设定在规定时间内自动删除。这里的这个规定时间,就是api在规定时间内不能重复提交。

 

自定义注解(注解作用于Controller层的API

1 @Target(ElementType.METHOD)
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface NoRepeatSubmission {
4  
5 }

 

  

切面逻辑

  1 import com.gotrade.apirepeatrequest.annotation.NoRepeatSubmission;
  2 import com.gotrade.apirepeatrequest.common.JacksonSerializer;
  3 import com.gotrade.apirepeatrequest.model.Result;
  4 import lombok.extern.slf4j.Slf4j;
  5 import org.aspectj.lang.ProceedingJoinPoint;
  6 import org.aspectj.lang.annotation.Around;
  7 import org.aspectj.lang.annotation.Aspect;
  8 import org.aspectj.lang.reflect.MethodSignature;
  9 import org.springframework.beans.factory.annotation.Autowired;
 10 import org.springframework.data.redis.core.RedisTemplate;
 11 import org.springframework.data.redis.core.ValueOperations;
 12 import org.springframework.stereotype.Component;
 13 import org.springframework.web.context.request.RequestContextHolder;
 14 import org.springframework.web.context.request.ServletRequestAttributes;
 15 
 16 import javax.servlet.http.HttpServletRequest;
 17 import java.nio.charset.StandardCharsets;
 18 import java.security.MessageDigest;
 19 import java.security.NoSuchAlgorithmException;
 20 import java.util.Objects;
 21 import java.util.concurrent.ConcurrentHashMap;
 22 import java.util.concurrent.TimeUnit;
 23 
 24 /**
 25  * @author jason.tang
 26  * @create 2019/4/8
 27  * @description
 28  */
 29 
 30 @Slf4j
 31 @Aspect
 32 @Component
 33 public class NoRepeatSubmissionAspect {
 34 
 35     @Autowired
 36     RedisTemplate<String, String> redisTemplate;
 37 
 38     /**
 39      * 环绕通知
 40      * @param pjp
 41      * @param ars
 42      * @return
 43      */
 44     @Around("execution(public * com.gotrade.apirepeatrequest.controller..*.*(..)) && @annotation(ars)")
 45     public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmission ars) {
 46         ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
 47         try {
 48             if (ars == null) {
 49                 return pjp.proceed();
 50             }
 51 
 52             HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
 53 
 54             String token = request.getHeader("Token");
 55             if (!checkToken(token)) {
 56                 return Result.failure("Token无效");
 57             }
 58             String servletPath = request.getServletPath();
 59             String jsonString = this.getRequestParasJSONString(pjp);
 60             String sha1 = this.generateSHA1(jsonString);
 61 
 62             // key = token + servlet path
 63             String key = token + "-" + servletPath + "-" + sha1;
 64 
 65             log.info("
{
	Servlet Path: {}
	Token: {}
	Json String: {}
	SHA-1: {}
	Result Key: {} 
}", servletPath, token, jsonString, sha1, key);
 66 
 67             // 如果Redis中有这个key, 则url视为重复请求
 68             if (opsForValue.get(key) == null) {
 69                 Object o = pjp.proceed();
 70                 opsForValue.set(key, String.valueOf(0), 3, TimeUnit.SECONDS);
 71                 return o;
 72             } else {
 73                 return Result.failure("请勿重复请求");
 74             }
 75         } catch (Throwable e) {
 76             e.printStackTrace();
 77             return Result.failure("验证重复请求时出现未知异常");
 78         }
 79     }
 80 
 81     /**
 82      * 获取请求参数
 83      * @param pjp
 84      * @return
 85      */
 86     private String getRequestParasJSONString(ProceedingJoinPoint pjp) {
 87         String[] parameterNames = ((MethodSignature) pjp.getSignature()).getParameterNames();
 88         ConcurrentHashMap<String, String> args = null;
 89 
 90         if (Objects.nonNull(parameterNames)) {
 91             args = new ConcurrentHashMap<>(parameterNames.length);
 92             for (int i = 0; i < parameterNames.length; i++) {
 93                 String value = pjp.getArgs()[i] != null ? pjp.getArgs()[i].toString() : "null";
 94                 args.put(parameterNames[i], value);
 95             }
 96         }
 97         return JacksonSerializer.toJSONString(args);
 98     }
 99 
100     private boolean checkToken(String token) {
101         if (token == null || token.isEmpty()) {
102             return false;
103         }
104         return true;
105     }
106 
107     private String generateSHA1(String str){
108         if (null == str || 0 == str.length()){
109             return null;
110         }
111         char[] hexDigits = { ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
112                 ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘};
113         try {
114             MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
115             mdTemp.update(str.getBytes(StandardCharsets.UTF_8));
116 
117             byte[] md = mdTemp.digest();
118             int j = md.length;
119             char[] buf = new char[j * 2];
120             int k = 0;
121             for (int i = 0; i < j; i++) {
122                 byte byte0 = md[i];
123                 buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
124                 buf[k++] = hexDigits[byte0 & 0xf];
125             }
126             return new String(buf);
127         } catch (NoSuchAlgorithmException e) {
128             e.printStackTrace();
129         }
130         return null;
131     }
132 }

 

 

 

切面主要逻辑代码,就是获取request中相关的信息,然后再拼接成一个key;判断在redis是否存在,不存在就添加并设置规定时间后自动移除,存在就是重复请求 。

 

以上是关于spring boot 通过AOP防止API重复请求的主要内容,如果未能解决你的问题,请参考以下文章

spring Aop实现防止重复提交

spring Aop实现防止重复提交

Spring Boot使用AOP实现REST接口简易灵活的安全认证

Aop+Redis防止接口重复提交

spring boot 防止重复提交

AOP统一处理请求日志