前言

在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(){
    }

Java后台防止客户端重复请求、提交表单的更多相关文章

  1. 搭建简单Django服务并通过HttpRequester实现GET/POST http请求提交表单

    调试Django框架写的服务时,需要模拟客户端发送POST请求,然而浏览器只能模拟简单的GET请求(将参数写在url内),网上搜索得到了HttpRequester这一firefox插件,完美的实现了模 ...

  2. 关于Asp.Net中避免用户连续多次点击按钮,重复提交表单的处理

    Web页面中经常碰到这类问题,就是客户端多次点击一个按钮或者链接,导致程序出现不可预知的麻烦. 客户就是上帝,他们也不是有意要给你的系统造成破坏,这么做的原因很大一部分是因为网络慢,点击一个操作之后, ...

  3. JavaWeb -- Struts1 使用示例: 表单校验 防表单重复提交 表单数据封装到实体

    1. struts 工作流程图 超链接 2. 入门案例 struts入门案例: 1.写一个注册页面,把请求交给 struts处理 <form action="${pageContext ...

  4. spring mvc 防止重复提交表单的两种方法,推荐第二种

    第一种方法:判断session中保存的token 比较麻烦,每次在提交表单时都必须传入上次的token.而且当一个页面使用ajax时,多个表单提交就会有问题. 注解Token代码: package c ...

  5. Struts2 token禁止重复提交表单

    如果服务器响应慢的情况下,用户会重复提交多个表单,这时候有两种设计思想: 1.在客户端使用JS技术,禁止客户重复提交表单.但是这样会使一些不使用浏览器方式登陆的人比如使用底层通信来攻击你的服务器 2. ...

  6. PHP防止用户重复提交表单

    我们提交表单的时候,不能忽视的一个限制是防止用户重复提交表单,因为有可能用户连续点击了提交按钮或者是攻击者恶意提交数据,那么我们在提交数据后的处理如修改或添加数据到数据库时就会惹上麻烦. 那么如何规避 ...

  7. JavaWeb 之 重复提交表单和验证码相关的问题!

    下面我们首先来说一下表单的重复提交问题,我们知道在真实的网络环境中可能受网速带宽的原因会造成页面中表单在提交的过程中出现网络的延迟等问题,从而造成多次提交的问题!下面我们就具体来分析一下造成表单提交的 ...

  8. php防止重复提交表单

    解决方案一:引入cookie机制来解决 提交页面代码如下a.php代码如下: <form id="form1" name="form1" method=& ...

  9. java模拟表单上传文件,java通过模拟post方式提交表单实现图片上传功能实例

    java模拟表单上传文件,java通过模拟post方式提交表单实现图片上传功能实例HttpClient 测试类,提供get post方法实例 package com.zdz.httpclient; i ...

随机推荐

  1. 2021 年写 JavaScript 代码的 17 个优化技巧

    我们经常会写一些 JavaScript 代码,但是如何写出干净又易维护的代码呢?本文将讲解 17 个 JavaScript 代码的技术帮助你提高编程水平,此外,本文可以帮助您为 2021 年的 Jav ...

  2. 灯光照射,圆形探测类问题(解题报告)<分层差分><cmath取整>

    题目描述 一个n*n的网格图上有m个探测器,每个探测器有个探测半径r,问这n*n个点中有多少个点能被探测到. 输入输出格式 输入格式: (1<=r<n<=5000) (1<=m ...

  3. Codeforces Round #643 (Div. 2) 题解 (ABCDE)

    目录 A. Sequence with Digits B. Young Explorers C. Count Triangles D. Game With Array E. Restorer Dist ...

  4. Codeforces Round #274 (Div. 2) C. Exams (贪心)

    题意:给\(n\)场考试的时间,每场考试可以提前考,但是记录的是原来的考试时间,问你如何安排考试,使得考试的记录时间递增,并且最后一场考试的时间最早. 题解:因为要满足记录的考试时间递增,所以我们用结 ...

  5. Entity Framework (EF) Core学习笔记 1

    1. Entity Framework (EF) Core 是轻量化.可扩展.开源和跨平台的数据访问技术,它还是一 种对象关系映射器 (ORM),它使 .NET 开发人员能够使用面向对象的思想处理数据 ...

  6. 洛谷P1522 [USACO2.4]牛的旅行 Cow Tours

    洛谷P1522 [USACO2.4]牛的旅行 Cow Tours 题意: 给出一些牧区的坐标,以及一个用邻接矩阵表示的牧区之间图.如果两个牧区之间有路存在那么这条路的长度就是两个牧区之间的欧几里得距离 ...

  7. 【原创】docker & kubernetes问题总结

    1.entrypoint & cmd 指令的区别 这主要考察 Dockerfile 良好实践中关于容器启动时运行的命令. entrypoint 和 cmd 命令都是设置容器启动时要执行的命令, ...

  8. OpenStack服务默认端口号

    在某些部署中,例如已设置限制性防火墙的部署,您可能需要手动配置防火墙以允许OpenStack服务流量. 要手动配置防火墙,您必须允许通过每个OpenStack服务使用的端口的流量.下表列出了每个Ope ...

  9. Netty(五)Netty 高性能之道

    4.背景介绍 4.1.1 Netty 惊人的性能数据 通过使用 Netty(NIO 框架)相比于传统基于 Java 序列化+BIO(同步阻塞 IO)的通信框架,性能提升了 8 倍多.事 实上,我对这个 ...

  10. codevs1039整数的k划分-思考如何去重复

    题目描述将整数n分成k份,且每份不能为空,任意两种划分方案不能相同(不考虑顺序).例如:n=7,k=3,下面三种划分方案被认为是相同的.1 1 51 5 15 1 1问有多少种不同的分法.输入描述输入 ...