1. 程式人生 > >防止表單重複提交的4種方法

防止表單重複提交的4種方法

1.背景與介紹:

平時開發的專案中可能會出現下面這些情況:

  1. 由於使用者誤操作,多次點選表單提交按鈕。
  2. 由於網速等原因造成頁面卡頓,使用者重複重新整理提交頁面。
  3. 黑客或惡意使用者使用postman等工具重複惡意提交表單(攻擊網站)。

這些情況都會導致表單重複提交,造成資料重複,增加伺服器負載,嚴重甚至會造成伺服器宕機。因此有效防止表單重複提交有一定的必要性。

2.解決方案

2.1 通過JavaScript遮蔽提交按鈕(不推薦)

通過js程式碼,當用戶點選提交按鈕後,遮蔽提交按鈕使使用者無法點選提交按鈕或點選無效,從而實現防止表單重複提交。

ps:js程式碼很容易被繞過。比如使用者通過重新整理頁面方式,或使用postman等工具繞過前段頁面仍能重複提交表單。因此不推薦此方法。

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
        <!DOCTYPE HTML>
        <html>
        <head>
         <title>表單</title>
            <script type="text/javascript">
            //預設提交狀態為false
            var commitStatus = false;
            function dosubmit(){
                  if(commitStatus==false){
                //提交表單後,講提交狀態改為true
                  commitStatus = true;
                  return true;
                 }else{
                  return false;
              }
             }
      </script>
     </head>
   
        <body>
            <form action="/path/post" onsubmit="return dosubmit()" method="post">
             使用者名稱:<input type="text" name="username">
            <input type="submit" value="提交" id="submit">
            </form>
        </body>
    </html>

2.2 給資料庫增加唯一鍵約束(簡單粗暴)

在資料庫建表的時候在ID欄位新增主鍵約束,使用者名稱、郵箱、電話等欄位加唯一性約束。確保資料庫只可以新增一條資料。

資料庫加唯一性約束sql:

alter table tableName_xxx add unique key uniq_xxx(field1, field2)

伺服器及時捕捉插入資料異常:

        try {
                xxxMapper.insert(user);
            } catch (DuplicateKeyException e) {
                logger.error("user already exist");
            }

通過資料庫加唯一鍵約束能有效避免資料庫重複插入相同資料。但無法阻止惡意使用者重複提交表單(攻擊網站),伺服器大量執行sql插入語句,增加伺服器和資料庫負荷。

2.3 利用Session防止表單重複提交(推薦)

實現原理:

伺服器返回表單頁面時,會先生成一個subToken保存於session,並把該subToen傳給表單頁面。當表單提交時會帶上subToken,伺服器攔截器Interceptor會攔截該請求,攔截器判斷session儲存的subToken和表單提交subToken是否一致。若不一致或session的subToken為空或表單未攜帶subToken則不通過。

首次提交表單時session的subToken與表單攜帶的subToken一致走正常流程,然後攔截器內會刪除session儲存的subToken。當再次提交表單時由於session的subToken為空則不通過。從而實現了防止表單重複提交。

使用:

mvc配置檔案加入攔截器配置

<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="xxx.xxx.interceptor.AvoidDuplicateSubmissionInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

攔截器

package xxx.xxxx.interceptor;

import xxx.xxx.SubToken;
import xxx.xxx.utils.Constants;
import org.apache.struts.util.TokenProcessor;
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;

public class AvoidDuplicateSubmissionInterceptor extends
        HandlerInterceptorAdapter {

    public AvoidDuplicateSubmissionInterceptor() {
    }

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            SubToken annotation = method
                    .getAnnotation(SubToken.class);
            if (annotation != null) {
                boolean needSaveSession = annotation.saveToken();
                if (needSaveSession) {
                    request.getSession(false)
                            .setAttribute(
                                    "subToken",
                                    TokenProcessor.getInstance().generateToken(
                                            request));
                }

                boolean needRemoveSession = annotation.removeToken();
                if (needRemoveSession) {
                    if (isRepeatSubmit(request)) {
                        return false;
                    }
                    request.getSession(false).removeAttribute("subToken");
                }
            }
        }
        return true;
    }

    private boolean isRepeatSubmit(HttpServletRequest request) {
        String serverToken = (String) request.getSession(false).getAttribute(
                "subToken");
        if (serverToken == null) {
            return true;
        }
        String clinetToken = request.getParameter("subToken");
        if (clinetToken == null) {
            return true;
        }
        if (!serverToken.equals(clinetToken)) {
            return true;
        }
        return false;
    }
}  

控制層 controller

@RequestMapping("/form")
//開啟一個Token
@SubToken(saveToken = true)
public String form() {
  return "/test/form";
}


@RequestMapping(value = "/postForm", method = RequestMethod.POST)
@ResponseBody
//開啟Token驗證,並且成功之後移除當前Token
@SubToken(removeToken = true)
public String postForm(String userName) {
  System.out.println(System.currentTimeMillis());
  try{
    System.out.println(userName);
    Thread.sleep(1500);//暫停1.5秒後程序繼續執行
  }catch (InterruptedException e) {
    e.printStackTrace();
 }
 System.out.println(System.currentTimeMillis());
 return "1";
}

表單頁面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form method="post" action="/postForm">
<input type="text" name="userName">
<input type="hidden" name="subToken" value="${subToken}">
<input type="submit" value="提交">
</form>
</body>
</html>

2.4使用AOP自定義切入實現

實現原理:

  1. 自定義防止重複提交標記(@AvoidRepeatableCommit)。
  2. 對需要防止重複提交的Congtroller裡的mapping方法加上該註解。
  3. 新增Aspect切入點,為@AvoidRepeatableCommit加入切入點。
  4. 每次提交表單時,Aspect都會儲存當前key到reids(須設定過期時間)。
  5. 重複提交時Aspect會判斷當前redis是否有該key,若有則攔截。

自定義標籤

        import java.lang.annotation.*;
        
        /**
         * 避免重複提交
         * @author hhz
         * @version
         * @since
         */
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface AvoidRepeatableCommit {
        
            /**
             * 指定時間內不可重複提交,單位毫秒
             * @return
             */
            long timeout()  default 30000 ;
        
        }

自定義切入點Aspect

        /**
         * 重複提交aop
         * @author hhz
         * @version 
         * @since 
         */
        @Aspect
        @Component
        public class AvoidRepeatableCommitAspect {
        
            @Autowired
            private RedisTemplate redisTemplate;
        
            /**
             * @param point
             */
            @Around("@annotation(com.xwolf.boot.annotation.AvoidRepeatableCommit)")
            public Object around(ProceedingJoinPoint point) throws Throwable {
        
                HttpServletRequest request  = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest();
                String ip = IPUtil.getIP(request);
                //獲取註解
                MethodSignature signature = (MethodSignature) point.getSignature();
                Method method = signature.getMethod();
                //目標類、方法
                String className = method.getDeclaringClass().getName();
                String name = method.getName();
                String ipKey = String.format("%s#%s",className,name);
                int hashCode = Math.abs(ipKey.hashCode());
                String key = String.format("%s_%d",ip,hashCode);
                log.info("ipKey={},hashCode={},key={}",ipKey,hashCode,key);
                AvoidRepeatableCommit avoidRepeatableCommit =  method.getAnnotation(AvoidRepeatableCommit.class);
                long timeout = avoidRepeatableCommit.timeout();
                if (timeout < 0){
                    timeout = Constants.AVOID_REPEATABLE_TIMEOUT;
                }
                String value = (String) redisTemplate.opsForValue().get(key);
                if (StringUtils.isNotBlank(value)){
                    return "請勿重複提交";
                }
                redisTemplate.opsForValue().set(key, UUIDUtil.uuid(),timeout,TimeUnit.MILLISECONDS);
                //執行方法
                Object object = point.proceed();
                return object;
            }
        
        }

相關推薦

Jsp 防止重複提交方案

SP避免Form重複提交的三種方案  1) javascript ,設定一個變數,只允許提交一次。    <script language="javascript">    var checksubmitflg = false;    function

防止重複提交4方法

1.背景與介紹: 平時開發的專案中可能會出現下面這些情況: 由於使用者誤操作,多次點選表單提交按鈕。 由於網速等原因造成頁面卡頓,使用者重複重新整理提交頁面。 黑客或惡意使用者使用postman等工具重複惡意提交表單(攻擊網站)。 這些情況都會導致表單重複提交,造成資料重複,增加伺服器負載,嚴重甚至會造成

防止重複提交的幾方法總結

版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/Warpar/article/details/72917924 1、JavaScript防止表單重複提交(主要用於網路延遲情況下使用者點選多次submit按鈕導致表單重複提

Struts2中防止重複提交的兩方式

防止表單重複提交,這是個很重要的知識點,而且很有用。當用戶提交了一個表單,此時,位址列顯示的是處理這個表單的Action的地址,若此時重新整理,則會重新發送一次表單資料,即又進行了一次提交,若這個Action是用來處理使用者註冊的,那麼重複提交會再一次向資料庫中插入之前已

防止重複提交的幾策略

表單重複提交在客戶端和伺服器端都會引發一些問題,一方面有些瀏覽器會彈出確認是否確認重新提交表單,另一方面伺服器端也可能會重複新增資料。 在瀏覽器端,當用戶點選提交一個表單,而伺服器沒有對提交做重定向,當用戶輸入資訊有誤提交後依然返回的是提交的頁面,或者在同一個頁面顯示提交成

PHP防止重複提交方法

下面的情況就會導致表單重複提交:       點選提交按鈕兩次。       點選重新整理按鈕。       使用瀏覽器後退按鈕重複之前的操作,導致重複提交表單。       使用瀏覽器歷史記錄重複提交表單。       瀏覽器重複的HTTP請求。      

jq提交時 禁用提交按鈕 防止重複提交 jq方法

防止表單重複提交 function(event,options){ $("#submit_login").attr({"disabled":"disabled"}); var needtime =

防止重複提交的八簡單有效的策略

表單重複提交是在多使用者Web應用中最常見、帶來很多麻煩的一個問題。有很多的應用場景都會遇到重複提交問題,比如: 點選提交按鈕兩次。 點選重新整理按鈕。 使用瀏覽器後退按鈕重複之前的操作,導致重複提交表單。 使用瀏覽器歷史記錄重複提交表單。 瀏覽器重複的HTTP請求。

利用session防止重複提交

使用者在提交表單的過程中,由於網路等原因,可能重複點選提交按鈕,向資料庫重複寫入或者讀取資料,為了防止這種情況發生。   解決方式: 1.客戶端防表單重複提交,在前端使用javascript限制。但是在前端並不能完全限制,比如下網頁原始碼更改,重複重新整理等。 2.服務端防

Java 使用Token令牌防止重複提交

Token驗證詳解 參考來源:https://blog.csdn.net/woshihaiyong168/article/details/52857479 使用Token令牌防止表單重複提交 參考來源:https://blog.csdn.net/cuiyaoqiang/article/d

用session防止重複提交

session案例1:防止表單重複提交   原理:     1,表單頁面由servlet程式生成,servlet為每次產生的表單頁面分配一個唯一的隨機標識號,並在FORM表單的一個隱藏欄位中設定這個標識號,同時在當前使用者的Session域中儲存這個標識號。

struts2-註解&防止重複提交

註解: 註解沒有分號 註解首字母是大寫,因為註解與類、介面是同一級的。一個註解後臺對應一個@interface類 同一語法單元,同一註解只能使用一次 在註解與語法單元間可以隔若干空行、註釋等非程式碼內容 在struts2中使用註解,主要完成對Act

防止重複提交---筆記

1. 防止表單重複提交 1.在使用者訪問頁面(設為頁面A)時session設定一個屬性(設為check) 值為 md5(當前時間)設為checkvalue, 且在表單中設定隱藏域 value為checkvalue 2.當用戶提交 在servlet裡檢測se

自定義註解攔截器,防止重複提交

1.自定義註解 package com.paotui.util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import ja

防止重複提交

問題:什麼是表單重複提交?                     regist.jsp----->RegistServlet         表單重複提交 危害: 刷票、 重複註冊、帶來伺服器訪問壓力(拒絕服務)                  解決方案:     

session案例:防止重複提交、一次性校驗碼

session案例1:防止表單重複提交 原理: 1,表單頁面由servlet程式生成,servlet為每次產生的表單頁面分配一個唯一的隨機標識號,並在FORM表單的一個隱藏欄位中設定這個標識號,同時在當前使用者的Session域中儲存這個標識號。 2,當用戶提交FOR

laravel中防止重複提交的綜合解決方案

怎樣防止表單重複提交,通過搜尋引擎能搜到很多結果,但很零散,系統性不強,正好前幾天做了這個功能,決定記錄下來。 根據資料流向的過程,分別在三個“點”控制表單的重複提交,如下: 第一,使用者觸發submit時,前端js控制提交按鈕的狀態,使用者觸發提交即設

Spring MVC攔截器+註解方式實現防止重複提交

表單重複提交是在多使用者Web應用中最常見、帶來很多麻煩的一個問題。有很多的應用場景都會遇到重複提交問題,比如: 1.點選提交按鈕兩次。2.點選重新整理按鈕。3.使用瀏覽器後退按鈕重複之前的操作,導致重複提交表單。4.使用瀏覽器歷史記錄重複提交表單。5.瀏覽器重複的HTTP

SSH框架之Struts的常用技術——資料回顯、防止重複提交

Struts2的常用三大技術: 1、資料回顯 2、模型驅動 3、防止表單重複提交 一、資料回顯: 1、資料回顯,必須要用struts標籤! 2、程式碼講解: 1)Action: //

session token 防止重複提交

本文正是通過使用session以及在session中加入token,來驗證同一個操作人員是否進行了併發重複的請求,在後一個請求到來時,使用session中的token驗證請求中的token是否一致,當不一致時,被認為是重複提交,將不准許通過。     整個流程可以由如下