Implementation principle of Java background to prevent clients from repeating requests and submitting forms

This article mainly introduces the implementation principle of preventing repeated requests and submitting forms from the client in the Java background. It is introduced in great detail through the example code, which has a certain reference value for everyone's study or work. Friends in need can refer to it

preface

In the web / APP project, some requests or operations will have an impact on the data (such as adding, deleting and modifying). Generally, some protection needs to be done for such requests to prevent the data disorder caused by users' intentional or unintentional repeated requests.

Common solutions

1. Client

For example, after the form is submitted, set the submit button to disable and so on

2. Server

The front-end restriction can only solve a few problems and is not thorough enough. The back-end's own anti duplication measures are essential and obligatory.

Here is a scheme I use in the project. Simply put, it is to determine whether the request URL and data are the same as the last time.

Method steps

1. Main logic:

Add an interceptor to all URLs. Each request stores the URL into the session. The next request verifies whether the URL data is the same. If the URL data is the same, access is denied.

Of course, I have made some optimizations on this basis, such as:

There are limitations in using session. When the number of users is large, the server will not support it. Here I use redis to replace it.

The token token mechanism is added.

2. Implementation steps:

2.1 customize an annotation

/**
 * @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 custom interceptor class

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,1,TimeUnit.SECONDS);
        return false;
      }
    }
  }
}

2.3 registered interceptors

@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;
  }
}

Annotate the corresponding method with @ sameurldata

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

The above is the whole content of this article. I hope it will help you in your study, and I hope you will support us a lot.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>