• Java后台防止客户端重复请求、提交表单


    前言

    在Web / App项目中,有一些请求或操作会对数据产生影响(比如新增、删除、修改),针对这类请求一般都需要做一些保护,以防止用户有意或无意的重复发起这样的请求导致的数据错乱。

    常见处理方案

    1.客户端

      例如表单提交后将提交按钮设为disable 等等方法...

    2.服务端

      前端的限制仅能解决少部分问题,且不够彻底,后端自有的防重复处理措施必不可少,义不容辞。

      在此提供一个我在项目中用到的方案。简单来说就是判断请求url和数据是否和上一次相同。

    方法步骤

    1.主要逻辑:

      给所有的url加一个拦截器,每次请求将url存入session,下次请求验证url数据是否相同,相同则拒绝访问。

      当然,我在此基础上做了一些优化,比如:

        使用session有局限性,用户量大了以后服务器会撑不住,在此我使用了redis来替换。

        加入了token令牌机制。

    2.实现步骤:

    • 2.1自定义一个注解
    •  1 /**
       2  * @Title: SameUrlData
       3  * @Description: 自定义注解防止表单重复提交
       4  * @Auther: xhq
       5  * @Version: 1.0
       6  * @create 2019/3/26 10:43
       7  */
       8 @Inherited
       9 @Target(ElementType.METHOD)
      10 @Retention(RetentionPolicy.RUNTIME)
      11 @Documented
      12 public @interface SameUrlData {
      13 
      14 }
    • 2.2自定义拦截器类
      • 检查此接口调用的方法是否使用了SameUrlData注解,若没有使用,表示此接口不需要校验;
      • 若使用了注解,获取请求url+参数,并去除一直在变化的参数(比如时间戳timeStamp和签名sign)
      • 检查参数中是否有token参数(token代表不同的用户的唯一标识),没有直接放行
      • 有token参数,将token+url作为redis的key,url+参数作为value存入redis,并设定自动销毁时间
      • (此处如果项目中没有redis,可参照我的另外一篇博客可解决:https://www.cnblogs.com/xhq1024/p/11115755.html
      • 再次访问进行验证是否重复请求  
    •   1 import com.alibaba.fastjson.JSONObject;
        2 import com.tuohang.hydra.framework.common.spring.SpringKit;
        3 import com.tuohang.hydra.toolkit.basis.string.StringKit;
        4 import org.slf4j.Logger;
        5 import org.slf4j.LoggerFactory;
        6 import org.springframework.data.redis.core.StringRedisTemplate;
        7 import org.springframework.stereotype.Component;
        8 import org.springframework.web.method.HandlerMethod;
        9 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
       10 
       11 import javax.servlet.http.HttpServletRequest;
       12 import javax.servlet.http.HttpServletResponse;
       13 import java.lang.reflect.Method;
       14 import java.util.HashMap;
       15 import java.util.Iterator;
       16 import java.util.Map;
       17 import java.util.concurrent.TimeUnit;
       18 
       19 /**
       20  * @Title: 防止用户重复提交数据拦截器
       21  * @Description: 将用户访问的url和参数结合token存入redis,每次访问进行验证是否重复请求接口
       22  * @Auther: xhq
       23  * @Version: 1.0
       24  * @create 2019/3/26 10:35
       25  */
       26 @Component
       27 public class SameUrlDataInterceptor extends HandlerInterceptorAdapter {
       28 
       29     private static Logger LOG = LoggerFactory.getLogger(SameUrlDataInterceptor.class);
       30 
       31     /**
       32      * 是否阻止提交,fasle阻止,true放行
       33      * @return
       34      */
       35     @Override
       36     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       37         if (handler instanceof HandlerMethod) {
       38             HandlerMethod handlerMethod = (HandlerMethod) handler;
       39             Method method = handlerMethod.getMethod();
       40             SameUrlData annotation = method.getAnnotation(SameUrlData.class);
       41             if (annotation != null) {
       42                 if(repeatDataValidator(request)){
       43                     //请求数据相同
       44                     LOG.warn("please don't repeat submit,url:"+ request.getServletPath());
       45                     JSONObject result = new JSONObject();
       46                     result.put("statusCode","500");
       47                     result.put("message","请勿重复请求");
       48                     response.setCharacterEncoding("UTF-8");
       49                     response.setContentType("application/json; charset=utf-8");
       50                     response.getWriter().write(result.toString());
       51                     response.getWriter().close();
       52 //                    拦截之后跳转页面
       53 //                    String formRequest = request.getRequestURI();
       54 //                    request.setAttribute("myurl", formRequest);
       55 //                    request.getRequestDispatcher("/WebRoot/common/error/jsp/error_message.jsp").forward(request, response);
       56                     return false;
       57                 }else {//如果不是重复相同数据
       58                     return true;
       59                 }
       60             }
       61             return true;
       62         } else {
       63             return super.preHandle(request, response, handler);
       64         }
       65     }
       66     /**
       67      * 验证同一个url数据是否相同提交,相同返回true
       68      * @param httpServletRequest
       69      * @return
       70      */
       71     public boolean repeatDataValidator(HttpServletRequest httpServletRequest){
       72         //获取请求参数map
       73         Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
       74         Iterator<Map.Entry<String, String[]>> it = parameterMap.entrySet().iterator();
       75         String token = "";
       76         Map<String, String[]> parameterMapNew = new HashMap<>();
       77         while(it.hasNext()){
       78             Map.Entry<String, String[]> entry = it.next();
       79             if(!entry.getKey().equals("timeStamp") && !entry.getKey().equals("sign")){
       80                 //去除sign和timeStamp这两个参数,因为这两个参数一直在变化
       81                 parameterMapNew.put(entry.getKey(), entry.getValue());
       82                 if(entry.getKey().equals("token")) {
       83                     token = entry.getValue()[0];
       84                 }
       85             }
       86         }
       87         if (StringKit.isBlank(token)){
       88             //如果没有token,直接放行
       89             return false;
       90         }
       91         //过滤过后的请求内容
       92         String params = JSONObject.toJSONString(parameterMapNew);
       93 
       94         System.out.println("params==========="+params);
       95 
       96         String url = httpServletRequest.getRequestURI();
       97         Map<String,String> map = new HashMap<>();
       98         //key为接口,value为参数
       99         map.put(url, params);
      100         String nowUrlParams = map.toString();
      101 
      102         StringRedisTemplate smsRedisTemplate = SpringKit.getBean(StringRedisTemplate.class);
      103         String redisKey = token + url;
      104         String preUrlParams = smsRedisTemplate.opsForValue().get(redisKey);
      105         if(preUrlParams == null){
      106             //如果上一个数据为null,表示还没有访问页面
      107             //存放并且设置有效期,2秒
      108             smsRedisTemplate.opsForValue().set(redisKey, nowUrlParams, 2, TimeUnit.SECONDS);
      109             return false;
      110         }else{//否则,已经访问过页面
      111             if(preUrlParams.equals(nowUrlParams)){
      112                 //如果上次url+数据和本次url+数据相同,则表示重复添加数据
      113                 return true;
      114             }else{//如果上次 url+数据 和本次url加数据不同,则不是重复提交
      115                 smsRedisTemplate.opsForValue().set(redisKey, nowUrlParams, 1, TimeUnit.SECONDS);
      116                 return false;
      117             }
      118         }
      119     }
      120 }
    • 2.3注册拦截器
       1 @Configuration
       2 public class WebMvcConfigExt extends WebMvcConfig {
       3 
       4     /**
       5      * 防止重复提交拦截器
       6      */
       7     @Autowired
       8     private SameUrlDataInterceptor sameUrlDataInterceptor;
       9 
      10     @Override
      11     public void addInterceptors(InterceptorRegistry registry) {
      12         // 避开静态资源
      13         List<String> resourcePaths = defineResourcePaths();
      14         registry.addInterceptor(sameUrlDataInterceptor).addPathPatterns("/**").excludePathPatterns(resourcePaths);// 重复请求
      15     }
      16 
      17     /**
      18      * 自定义静态资源路径
      19      * 
      20      * @return
      21      */
      22     @Override
      23     public List<String> defineResourcePaths() {
      24         List<String> patterns = new ArrayList<>();
      25         patterns.add("/assets/**");
      26         patterns.add("/upload/**");
      27         patterns.add("/static/**");
      28         patterns.add("/common/**");
      29         patterns.add("/error");
      30         return patterns;
      31     }
      32 }
    • 在相应方法上加@SameUrlData注解
      @SameUrlData
      @ResponseBody
      @RequestMapping(value = "/saveOrUpdate")
      public String saveOrUpdate(){
      }
  • 相关阅读:
    C#限速下载网络文件
    MVC与WebApi中的异常统一处理
    Javascript闭包(Closure)
    HTML转义字符 Unicode和CSS伪类介绍
    .NET通用工具——正则表达式
    C#的格式化(进制转换|位运算)
    javascript中的类型转换(进制转换|位运算)
    MVC中的七层架构
    Excel中的常用功能
    jQuery基础
  • 原文地址:https://www.cnblogs.com/xhq1024/p/10650127.html
Copyright © 2020-2023  润新知