首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> JSP 程式設計 >> 關於JSP

防止 JSP 表單重複提交:七大方案完整對比

     編輯:關於JSP

使用者雙擊提交按鈕、按 F5 重整、瀏覽器後退重送,都會導致重複提交。本文系統整理客戶端與伺服器端七種防重策略,並分析優缺點與適用場景。

現象與風險

  • 重複下單、重複扣款、重複寫入資料庫
  • 非冪等操作(INSERT、UPDATE)最嚴重
  • 查詢類(GET)通常無副作用,但仍會增加伺服器負載

方案一:客戶端 Meta 標籤禁用快取(效果有限)

<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate">
<meta http-equiv="Expires" content="0">

缺點:無法阻止使用者主動點擊、重整、後退;僅防止瀏覽器快取頁面。

方案二:JavaScript 禁用提交按鈕(體驗佳,但可繞過)

<input type="submit" value="提交" onclick="this.disabled=true; this.form.submit();">

或使用旗標變數:

var submitted = false;
function checkSubmit() {
    if (submitted) return false;
    submitted = true;
    return true;
}
<form onsubmit="return checkSubmit()">...

缺點:JS 被禁用或駭客繞過前端即失效。

方案三:Post-Redirect-Get (PRG) 模式(推薦基礎方案)

處理完 POST 請求後,重導向 (302) 到結果頁(GET),而非直接 Forward:

// Servlet / Controller
response.sendRedirect(request.getContextPath() + "/result.jsp");

Struts 1 配置:

<forward name="success" path="/list.jsp" redirect="true"/>

原理:瀏覽器歷史記錄只保留 GET 請求,重整不會重送 POST。

缺點:需在 Session/Flash Scope 傳遞提示訊息;無法攔截「後退到表單頁再提交」。

方案四:Session Token(同步令牌模式,業界標準)

流程:

  1. 表單頁渲染時,生成唯一 Token 存入 Session,並寫入 Hidden 欄位。
  2. 提交時比對 Request Token 與 Session Token。
  3. 比對成功:執行業務邏輯,刷新 Session Token(或移除)。
  4. 比對失敗:視為重複提交,導向錯誤頁。

Struts 1 內建實作

<!-- JSP -->
<html:form action="/save"> ... </html:form>
<!-- 自動產生 <input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="..."> -->

// Action
if (isTokenValid(request, true)) { // true = 自動重置 token
    // 業務邏輯
    return mapping.findForward("success");
} else {
    saveToken(request); // 重新生成供重試
    return mapping.findForward("duplicate");
}

手動實作(通用 Servlet)

// 表單頁
String token = UUID.randomUUID().toString();
session.setAttribute("FORM_TOKEN", token);
// JSP: <input type="hidden" name="token" value="${sessionScope.FORM_TOKEN}">

// 處理 Servlet
String submittedToken = request.getParameter("token");
String sessionToken = (String) session.getAttribute("FORM_TOKEN");
if (sessionToken != null && sessionToken.equals(submittedToken)) {
    session.removeAttribute("FORM_TOKEN"); // 或更新新 token
    // 執行業務
} else {
    // 重複提交處理
}

方案五:資料庫層面唯一約束(終極防線)

訂單號、流水號加 UNIQUE INDEX,重複寫入拋異常回滾。配合冪等性設計(如「支付狀態機」),確保同一筆業務只能成功一次。

方案六:AJAX 無刷新提交 + 介面鎖定

$.ajax({
    url: '/api/submit',
    type: 'POST',
    data: $('#form').serialize(),
    beforeSend: function() { $('#btnSubmit').prop('disabled', true); },
    complete: function() { $('#btnSubmit').prop('disabled', false); },
    success: function(resp) { /* 處理結果 */ }
});

結合前端鎖定與後端 Token 雙重保險。

方案七:防止瀏覽器後退(歷史記錄操作)

<script>
// 頁面載入時推進歷史,阻止後退
window.history.pushState(null, null, location.href);
window.onpopstate = function() { window.history.pushState(null, null, location.href); };
</script>

注意:此為體驗輔助,無法替代伺服器端驗證。

方案選型建議

場景推薦組合
一般表單(增刪改)PRG + Session Token + DB 唯一約束
高併發下單/支付Token + 分散式鎖 (Redis) + 冪等鍵
純查詢/GET無需防重,加快取快取
舊系統快速修補JS 禁用按鈕 + PRG

總結

前端攔截只能誠實使用者,伺服器端 Token 才是核心防線。採用「PRG 模式 + Session Token + 資料庫唯一約束」三層防禦,可覆蓋 99% 重複提交場景。現代框架(Spring MVC、Struts 2、Spring Boot)皆內建 CSRF/Token 支援,優先使用框架機制,避免重複造輪子。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved