Java后台防止客戶端重復請求、提交表單


前言

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM