前言

在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. PHP-表单提交(form)

    PHP-表单提交 一  form表单 GET    将表单内容附加到URL地址后面,提交的信息长度有限制,不可以超过8192个字节,同时不具有保密性,而且只能传送ASCII字符(一般传送的不保密性数据 ...

  2. thymeleaf第一篇:什么是-->为什么要使用-->有啥好处这玩意

    Thymeleaf3.0版本官方地址 1 Introducing Thymeleaf Thymeleaf 是一个跟 Velocity.FreeMarker 类似的模板引擎,它可以完全替代 JSP . ...

  3. HDU-6148 Valley Number (数位DP)

    当一个数字,从左到右依次看过去数字没有出现先递增接着递减的"山峰"现象,就被称作 Valley Number.它可以递增,也可以递减,还可以先递减再递增.在递增或递减的过程中可以出 ...

  4. Codeforces Round #657 (Div. 2) A. Acacius and String(字符串)

    题目链接:https://codeforces.com/contest/1379/problem/A 题意 给出一个由 '?' 和小写字母组成的字符串,可以将 '?' 替换为小写字母,判断是否存在一种 ...

  5. Codeforces Round #627 (Div. 3) F - Maximum White Subtree(深度优先搜索)

    题意: n 个点 n - 1 条边的树,问每个点所在所有子树中白黑点数目的最大差. 思路: 白点先由下至上汇集,后由上至下分并. #include <bits/stdc++.h> usin ...

  6. 【hdu 4859】海岸线(图论--网络流最小割)

    题意:有一个区域,有'.'的陆地,'D'的深海域,'E'的浅海域.其中浅海域可以填充为陆地.这里的陆地区域不联通,并且整个地图都处在海洋之中.问填充一定浅海域之后所有岛屿的最长的海岸线之和. 解法:最 ...

  7. Educational Codeforces Round 89 (Rated for Div. 2) A. Shovels and Swords (贪心)

    题意:你有\(a\)个树枝和\(b\)个钻石,\(2\)个树枝和\(1\)个钻石能造一个铁铲,\(1\)个树枝和\(2\)个钻石能造一把剑,问最多能造多少铲子和剑. 题解:如果\(a\le b\),若 ...

  8. Codeforces Round #650 (Div. 3) D. Task On The Board (构造,贪心)

    题意:有一个字符串和一组数,可以对字符串删去任意字符后为数组的长度,且可以随意排序,要求修改后的字符串的每个位置上的字符满足:其余大于它的字符的位置减去当前位置绝对值之和等于对应序列位置上的数. 题解 ...

  9. DNA Sequence POJ - 2778 AC自动机 && 矩阵快速幂

    It's well known that DNA Sequence is a sequence only contains A, C, T and G, and it's very useful to ...

  10. PowerShell随笔8 --- function

    为了脚本逻辑的重复使用,我们更多时候会封装成方法.PowerShell的function和C#.JavaScript的定义有些区别. 我们直接看例子: 可以看到,定义方法并不是这样的: functio ...