Java后台防止客户端重复请求、提交表单实现原理

这篇文章主要介绍了Java后台防止客户端重复请求、提交表单实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

前言

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

常见处理方案

1.客户端

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

2.服务端

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

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

方法步骤

1.主要逻辑:

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

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

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

    加入了token令牌机制。

2.实现步骤:

2.1自定义一个注解

/**
 * @Title: SameUrlData
 * @Description: 自定义注解防止表单重复提交
 * @Auther: xhq
 * @Version: 1.0
 * @create 2019/3/26 10:43
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SameUrlData {

}

2.2自定义拦截器类

  • 检查此接口调用的方法是否使用了SameUrlData注解,若没有使用,表示此接口不需要校验;
  • 若使用了注解,获取请求url+参数,并去除一直在变化的参数(比如时间戳timeStamp和签名sign)
  • 检查参数中是否有token参数(token代表不同的用户的唯一标识),没有直接放行
  • 有token参数,将token+url作为redis的key,url+参数作为value存入redis,并设定自动销毁时间
  • 再次访问进行验证是否重复请求  
import com.alibaba.fastjson.JSONObject;
import com.tuohang.hydra.framework.common.spring.SpringKit;
import com.tuohang.hydra.toolkit.basis.string.StringKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Title: 防止用户重复提交数据拦截器
 * @Description: 将用户访问的url和参数结合token存入redis,每次访问进行验证是否重复请求接口
 * @Auther: xhq
 * @Version: 1.0
 * @create 2019/3/26 10:35
 */
@Component
public class SameUrlDataInterceptor extends HandlerInterceptorAdapter {

  private static Logger LOG = LoggerFactory.getLogger(SameUrlDataInterceptor.class);

  /**
   * 是否阻止提交,fasle阻止,true放行
   * @return
   */
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    if (handler instanceof HandlerMethod) {
      HandlerMethod handlerMethod = (HandlerMethod) handler;
      Method method = handlerMethod.getMethod();
      SameUrlData annotation = method.getAnnotation(SameUrlData.class);
      if (annotation != null) {
        if(repeatDataValidator(request)){
          //请求数据相同
          LOG.warn("please don't repeat submit,url:"+ request.getServletPath());
          JSONObject result = new JSONObject();
          result.put("statusCode","500");
          result.put("message","请勿重复请求");
          response.setCharacterEncoding("UTF-8");
          response.setContentType("application/json; charset=utf-8");
          response.getWriter().write(result.toString());
          response.getWriter().close();
//          拦截之后跳转页面
//          String formRequest = request.getRequestURI();
//          request.setAttribute("myurl", formRequest);
//          request.getRequestDispatcher("/WebRoot/common/error/jsp/error_message.jsp").forward(request, response);
          return false;
        }else {//如果不是重复相同数据
          return true;
        }
      }
      return true;
    } else {
      return super.preHandle(request, response, handler);
    }
  }
  /**
   * 验证同一个url数据是否相同提交,相同返回true
   * @param httpServletRequest
   * @return
   */
  public boolean repeatDataValidator(HttpServletRequest httpServletRequest){
    //获取请求参数map
    Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
    Iterator<Map.Entry<String, String[]>> it = parameterMap.entrySet().iterator();
    String token = "";
    Map<String, String[]> parameterMapNew = new HashMap<>();
    while(it.hasNext()){
      Map.Entry<String, String[]> entry = it.next();
      if(!entry.getKey().equals("timeStamp") && !entry.getKey().equals("sign")){
        //去除sign和timeStamp这两个参数,因为这两个参数一直在变化
        parameterMapNew.put(entry.getKey(), entry.getValue());
        if(entry.getKey().equals("token")) {
          token = entry.getValue()[0];
        }
      }
    }
    if (StringKit.isBlank(token)){
      //如果没有token,直接放行
      return false;
    }
    //过滤过后的请求内容
    String params = JSONObject.toJSONString(parameterMapNew);

    System.out.println("params==========="+params);

    String url = httpServletRequest.getRequestURI();
    Map<String,String> map = new HashMap<>();
    //key为接口,value为参数
    map.put(url, params);
    String nowUrlParams = map.toString();

    StringRedisTemplate smsRedisTemplate = SpringKit.getBean(StringRedisTemplate.class);
    String redisKey = token + url;
    String preUrlParams = smsRedisTemplate.opsForValue().get(redisKey);
    if(preUrlParams == null){
      //如果上一个数据为null,表示还没有访问页面
      //存放并且设置有效期,2秒
      smsRedisTemplate.opsForValue().set(redisKey, nowUrlParams, 2, TimeUnit.SECONDS);
      return false;
    }else{//否则,已经访问过页面
      if(preUrlParams.equals(nowUrlParams)){
        //如果上次url+数据和本次url+数据相同,则表示重复添加数据
        return true;
      }else{//如果上次 url+数据 和本次url加数据不同,则不是重复提交
        smsRedisTemplate.opsForValue().set(redisKey, nowUrlParams, 1, TimeUnit.SECONDS);
        return false;
      }
    }
  }
}

2.3注册拦截器

@Configuration
public class WebMvcConfigExt extends WebMvcConfig {

  /**
   * 防止重复提交拦截器
   */
  @Autowired
  private SameUrlDataInterceptor sameUrlDataInterceptor;

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 避开静态资源
    List<String> resourcePaths = defineResourcePaths();
    registry.addInterceptor(sameUrlDataInterceptor).addPathPatterns("/**").excludePathPatterns(resourcePaths);// 重复请求
  }

  /**
   * 自定义静态资源路径
   *
   * @return
   */
  @Override
  public List<String> defineResourcePaths() {
    List<String> patterns = new ArrayList<>();
    patterns.add("/assets/**");
    patterns.add("/upload/**");
    patterns.add("/static/**");
    patterns.add("/common/**");
    patterns.add("/error");
    return patterns;
  }
}

在相应方法上加@SameUrlData注解

@SameUrlData
@ResponseBody
@RequestMapping(value = "/saveOrUpdate")
public String saveOrUpdate(){
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • javaweb中ajax请求后台servlet(实例)

    废话不多说,直接上代码 public class DZFP_jdbc extends HttpServlet{ private static final long serialVersionUID = 1L; public static Connection conn; public static ResultSet rs = null ; public static PreparedStatement ps = null ; private static String url = "jdbc:

  • Java编程Socket实现多个客户端连接同一个服务端代码

    Java Socket(套接字)通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄.应用程序通常通过"套接字"向网络发出请求或者应答网络请求. 使用Socket实现多个客户端和同一客户端通讯:首先客户端连接服务端发送一条消息,服务端接收到消息后进行处理,完成后再回复客户端一条消息.本人通过自己的思维编写了一份服务端和客户端实现的代码,望能与大家相互学习,共同进步. 服务端代码 /** * Socket服务端 * 功能说明: * */ public cl

  • Java poi导出Excel下载到客户端

    Java poi 导出Excel并下载到客户端,具体内容如下 Maven配置,包含了其他文件格式的依赖,就全贴出来了 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-excelant</artifactId> <version>3.12</version> </dependency> <dependency> <gr

  • java实现客户端向服务器发送文件

    本文实例为大家分享了java实现客户端向服务器发送文件的具体代码,供大家参考,具体内容如下 服务器源代码: import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.In

  • java后台调用HttpURLConnection类模拟浏览器请求实例(可用于接口调用)

    一般在项目开发中难免遇到外部接口的调用,本文实例讲述了java后台调用HttpURLConnection类模拟浏览器请求的方法.可用于接口调用.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: package com.cplatform.movie.back.test; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.InputStreamReader; import ja

  • Java基于socket实现的客户端和服务端通信功能完整实例

    本文实例讲述了Java基于socket实现的客户端和服务端通信功能.分享给大家供大家参考,具体如下: 以下代码参考马士兵的聊天项目,先运行ChatServer.java实现端口监听,然后再运行ChatClient.java 客户端实例 ChatClient.java package socketDemo; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; public class Ch

  • java实现上传文件到服务器和客户端

    JAVA编写一个可以上传文件的服务器和客户端,具体内容如下 服务端 class Server { public static void main(String[] args) throws Exception { //建立服务端Socket ServerSocket ss = new ServerSocket(10005); //接收客户端Socket Socket fileLoaderSocket = ss.accept(); //打印连接信息 String ip = fileLoaderSo

  • Java下http下载文件客户端和上传文件客户端实例代码

    一.下载客户端代码 package javadownload; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; /** * @说明 导出虚拟机 * @author wxt * @version 1

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

    这篇文章主要介绍了Java后台防止客户端重复请求.提交表单实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 前言 在Web / App项目中,有一些请求或操作会对数据产生影响(比如新增.删除.修改),针对这类请求一般都需要做一些保护,以防止用户有意或无意的重复发起这样的请求导致的数据错乱. 常见处理方案 1.客户端 例如表单提交后将提交按钮设为disable 等等方法... 2.服务端 前端的限制仅能解决少部分问题,且不够彻底,后端自有的

  • asp.net 防止用户通过后退按钮重复提交表单

    防止用户通过后退按钮重复提交表单 <% response.Buffer=true response.Expires=0 response.ExpiresAbsolute=now()-1 response.CacheControl="no-cache" %> response.Buffer=true的意思就是指明输出页面是否被缓冲,当属性值为True时,服务器将不会向客户端发送任何信息,直到所有程序执行完或者遇到 <% Response.Flush %>或<

  • ThinkPHP防止重复提交表单的方法实例分析

    本文实例总结分析了ThinkPHP防止重复提交表单的方法.分享给大家供大家参考,具体如下: 为什么会有表单重复的坑 在开发中,如果一个新增或修改的表单,在后台完成数据库操作后我们设定的不是跳转到其他页面,还是返回本页面,这时点击浏览器的后退再提交或刷新页面,会导致form表单重复提交,即这条记录会被增加或修改两次. 导致表单重复提交的原因是:第一次提交的表单会被缓存到内存中,直到页面下次提交或页面关闭或转向其他页面时才消失.在自调用返回时,内存中的数据依然在,这时页面中的判断提交的代码依然可以检

  • java通过模拟post方式提交表单实现图片上传功能实例

    本文实例讲述了java通过模拟post方式提交表单实现图片上传功能.分享给大家供大家参考,具体如下: 模拟表单html如下: <form action="up_result.jsp" method="post" enctype="multipart/form-data" name="form1" id="form1"> <label> <input type="tex

  • Java传入用户名和密码并自动提交表单实现登录到其他系统的实例代码

    不用单点登录,模拟远程项目的登录页面表单,在访问这个页面的时候自动提交表单到此项目的登录action,就可以实现登录到其他系统. ssh框架项目 1.以下是本地系统的action代码: import java.io.IOException; import java.util.List; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.i

  • php防止用户重复提交表单

    我们提交表单的时候,不能忽视的一个限制是防止用户重复提交表单,因为有可能用户连续点击了提交按钮或者是攻击者恶意提交数据,那么我们在提交数据后的处理如修改或添加数据到数据库时就会惹上麻烦. 效果图: 那么如何规避这中重复提交表单的现象出现呢?我们可以从很多方面入手: 首先从前端做限制.前端JavaScript在按钮被点击一次后禁用,即disabled,这个方法简单的防止了多次点击提交按钮,但是缺点是如果用户禁用了javascript脚本则失效. 第二,我们可以在提交后做redirect页面重定向,

  • java后台判断客户端是手机/PC并返回不同页面的实例

    这个代码其实不是由我本人发现的,而是在翻看公司项目时无意间发现,觉得很实用所以拿出来大家分享. 框架:Struts2+spring+ibatis, 主要实现是依靠Http请求头Header中的 "User-Agent" 来完成,好了, 废话不多说直接上代码. Action中: public String execute() { HttpServletRequest request = ServletActionContext.getRequest(); boolean isMoblie

  • PHP如何防止用户重复提交表单

    我们提交表单的时候,不能忽视的一个限制是防止用户重复提交表单,因为有可能用户连续点击了提交按钮或者是攻击者恶意提交数据,那么我们在提交数据后的处理如修改或添加数据到数据库时就会惹上麻烦. 那么如何规避这中重复提交表单的现象出现呢?我们可以从很多方面入手: 首先从前端做限制.前端JavaScript在按钮被点击一次后禁用,即disabled,这个方法简单的防止了多次点击提交按钮,但是缺点是如果用户禁用了javascript脚本则失效. 第二,我们可以在提交后做redirect页面重定向,即提交后跳

  • jquery提交表单mvc3后台处理示例

    JQuery提交表单 复制代码 代码如下: $(document).ready(function () {           $("#btnLogin").click(function () {               $.ajax({                   url: '/Home/Login',                   data: '{ "account":"' + $("#account").val(

  • jQuery异步提交表单实例

    前言: 我们在开发的时候,一定会使用ajax异步提交表单,在这里总结一下: 前提准备:引入脚本 <!--jquery需要引入的文件--> <script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.js"></script> <!--ajax提交表单需要引入jquery.form.js--> <script type="text/javascript&q

随机推荐