栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

前言

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

常见处理方案

1.客户端

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

2.服务端

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

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

方法步骤

1.主要逻辑:

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

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

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

    加入了token令牌机制。

2.实现步骤:

2.1自定义一个注解


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


@Component
public class SameUrlDataInterceptor extends HandlerInterceptorAdapter {

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

  
  @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);
    }
  }
  
  public boolean repeatDataValidator(HttpServletRequest httpServletRequest){
    //获取请求参数map
    Map parameterMap = httpServletRequest.getParameterMap();
    Iterator> it = parameterMap.entrySet().iterator();
    String token = "";
    Map parameterMapNew = new HashMap<>();
    while(it.hasNext()){
      Map.Entry 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 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 resourcePaths = defineResourcePaths();
    registry.addInterceptor(sameUrlDataInterceptor).addPathPatterns("
  @Override
  public List defineResourcePaths() {
    List 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(){
}

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/135736.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号