防止Web表單重復提交的方法總結


在Web開發中,對於處理表單重復提交是經常要面對的事情。那么,存在哪些場景會導致表單重復提交呢?表單重復提交會帶來什么問題?有哪些方法可以避免表單重復提交?
Web表單重復提交

表單重復提交的場景

1.場景一:服務端未能及時響應結果(網絡延遲,並發排隊等因素),導致前端頁面沒有及時刷新,用戶有機會多次提交表單
用戶重復提交表單

2.場景二:提交表單成功之后用戶再次點擊刷新按鈕導致表單重復提交
提交成功之后刷新重復提交

3.場景三:提交表單成功之后點擊后退按鈕回退到表單頁面再次提交
回退到表單頁面再次提交

表單重復提交的弊端

下面通過一個簡單的示例進行說明。

  • 表單頁面: test-form-submit-repeat.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>處理表單重復提交</title>
</head>
<body>

    <form action="<%=request.getContextPath()%>/formServlet.do" method="post">
        姓名:<input type="text" name="username" />
        <input type="submit" value="提交">
    </form>
</body>
</html>
  • 后台Serlvet:FormServlet.java
public class FormServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        String userName = req.getParameter("username");

        try {
            // 模擬服務端處理效率慢
            Thread.sleep(3 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("插入數據:" + userName);
    }
}

實驗表單重復提交結果:
實驗表單重提交

顯然,從演示結果來看,如果出現表單重復提交,將會導致相同的數據被重復插入到數據庫中。實際上,這是不應該發生的。

如何避免重復提交表單

關於解決表單重復提交,分為在前端攔截和服務端攔截2種方式。

1.在前端對表單重復提交進行攔截

在前端攔截表單重復提交可以通過多種方式實現:
(1)通過設置變量標志位進行攔截

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>處理表單重復提交</title>
</head>
<body>
    <form action="<%=request.getContextPath()%>/formServlet.do" method="post" onsubmit="return checkSubmit();">
        姓名:<input type="text" name="username" />
        <input type="submit" value="提交">
    </form>
</body>
<script type="text/javascript">
    // 設置表單提交標志
    var submit = false;
    function checkSubmit() {
        if(!submit) {
            // 表單提交后設置標志位
            submit = true;
            return true;
        }
        // 表單已經提交,不允許再次提交
        console.log("請不要重復提交表單!");
        return false;
    }
</script>
</html>

在前端設置變量標志攔截表單重復提交

(2)通過禁用按鈕進行攔截
除了在前端通過設置標志位進行攔截之外,還可以在表單提交之后將按鈕disabled掉,這樣就徹底阻止了表單被重復提交的可能。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>處理表單重復提交</title>
</head>
<body>
    <form action="<%=request.getContextPath()%>/formServlet.do" method="post" onsubmit="return disabledSubmit();">
        姓名:<input type="text" name="username" />
        <input id="submitBtn" type="submit" value="提交">
    </form>
</body>
<script type="text/javascript">
    function disabledSubmit() {
        // 在提交按鈕第一次執行之后就disabled掉,避免重復提交
        document.getElementById("submitBtn").disabled= "disabled";
        return true;
    }
</script>
</html>

提交表單之后disabled掉按鈕

當然,還可以直接在提一次提交之后將按鈕隱藏掉。但是,是否需要這樣做,需要考慮用戶的操作體驗是不是可以接受。

在前端攔截雖然可以解決場景一的表單重復提交問題,但是針對場景二(刷新)和場景三(后退重新提交)的表單重復提交是無能為力的。
前端攔截只對場景一有效

2.在服務器端對表單重復提交進行攔截
在服務器端攔截表單重復提交的請求,實際上是通過在服務端保存一個token來實現的,而且這個在服務端保存的token需要通過前端傳遞,分三步走:

第一步:訪問頁面時在服務端保存一個隨機token

public class FormServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        UUID uuid = UUID.randomUUID();
        String token = uuid.toString().replaceAll("-", "");
        // 訪問頁面時隨機生成一個token保存在服務端session中
        req.getSession().setAttribute("token", token);
        req.getRequestDispatcher("/test-form-submit-repeat.jsp").forward(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       doGet(req, resp);
    }
}

隨機token的產生可以使用任何恰當的方式,在這里通過UUID產生。

第二步:將服務端端保存的隨機token通過前端頁面傳遞

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>處理表單重復提交</title>
</head>
<body>
    <form action="<%=request.getContextPath()%>/doFormServlet.do" method="post">
        <!-- 隱藏域保存服務端token -->
        <input type="hidden" name="token" value="<%=session.getAttribute("token")%>" />
        姓名:<input type="text" name="username" />
        <input id="submitBtn" type="submit" value="提交">
    </form>
</body>
</html>

第三步:提交表單時在服務端通過檢查token來判斷是否為重復提交的表單請求

public class DoFormServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");

        if(checkRepeatSubmit(req)) {
            System.out.println("請不要重復提交!");
            return;
        }
        
        // 在第一次處理表單之后需要清空token,這一步非常關鍵
        req.getSession().removeAttribute("token");

        String userName = req.getParameter("username");
        try {
            // 模擬服務端處理效率慢
            Thread.sleep(3 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("插入數據:" + userName);
    }

    // 檢查表單是否為重復提交
    private boolean checkRepeatSubmit(HttpServletRequest req) {
        Object sessionTokenObj = req.getSession().getAttribute("token");
        if(sessionTokenObj == null) {
            // 表單重復提交
            System.out.println("Session token is NULL!");
            return true;
        }

        String paramToken = req.getParameter("token");
        if(paramToken == null) {
            // 非法請求
            System.out.println("Parameter token is NULL!");
            return true;
        }

        if(!paramToken.equals(sessionTokenObj.toString())) {
            // 非法請求
            System.out.println("Token not same");
            return true;
        }
        return false;
    }
}

在服務端攔截表單重復提交

顯然,通過在服務端保存token的方式攔截場景二和場景三的表單重復提交是非常有效的。而且,這種方式同樣可以攔截場景一的表單重復提交。
通過服務器端攔截的方式解決場景一的表單重復提交

也就是說,對於攔截表單重復提交的終極解決方案是在服務器端進行攔截!不過,考慮到用戶操作體驗的問題,可能需要同時在前端進行攔截,這可以根據具體的產品設計而定。
在服務端攔截表單重復提交的原理

另外,有意思的是:在最新的Firefox瀏覽版本(Firefox Quantum 59.0.1 64位)中,瀏覽器自己就能處理場景一的表單重復提交(但是不能處理場景二和場景三的表單重復提交)。
火狐瀏覽器處理表單重復提交
經過驗證,在最新版的Chrome(Chrome 65.0.3325.181)瀏覽器中還不具備這個功能。


免責聲明!

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



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