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

Java 表單重複提交防範指南:Token 機制完整實作

     編輯:Java 相關知識

在 Web 開發中,表單重複提交是常見且棘手的問題。使用者可能因網路延遲而連點提交按鈕,或在成功頁面重新整理、瀏覽器後退後再次送出,導致資料重複寫入資料庫。本文將系統性說明重複提交的成因,並以 Token(令牌)機制 為核心,從 JSP 內嵌寫法、Servlet 分離、工具類封裝三個階段,示範如何徹底解決此問題。

為什麼會發生重複提交?

  • 使用者連點:伺服器回應緩慢或網路延遲,使用者以為沒送出而連續點擊。
  • 成功頁面重新整理:提交成功後以 forward 轉發至結果頁,使用者按 F5 會重送最後一次 POST 請求。
  • 瀏覽器後退再送出:使用者按「上一頁」回到表單,再次點擊提交。
注意:若後退後重新整理表單頁面再送出,屬於新請求,非重複提交。Firefox 對同一網址重複送出有防呆機制。

核心解決思路:Token 機制

原理與驗證碼類似——「先取得表單頁面,才能提交表單」。流程如下:

  1. 使用者請求表單頁面時,伺服器產生唯一 Token,一份存入 Session,一份放入表單隱藏欄位。
  2. 使用者送出表單,攜帶表單 Token。
  3. Servlet 取得 Session Token 並比對:
    • 相同 → 合法請求,處理業務並銷毀 Session Token。
    • 不同或 Session 無 Token → 重複提交,攔截並提示。

階段一:JSP 內嵌 Java 代碼(教學示範)

<% 
  String token = java.util.UUID.randomUUID().toString();
  session.setAttribute("TOKEN_IN_SESSION", token);
%>
<h3>轉賬界面</h3>
<form action="/trans" method="post">
  <input type="hidden" name="token" value="<%=token%>"/>
  轉賬金額:<input type="text" name="money" min="1" required /><br/>
  <input type="submit" value="轉賬" />
</form>
@WebServlet("/trans")
public class TransferServlet extends HttpServlet {
  protected void service(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
    req.setCharacterEncoding("UTF-8");
    resp.setContentType("text/html;charset=UTF-8");
    PrintWriter out = resp.getWriter();

    String token = req.getParameter("token");
    String sessionToken = (String) req.getSession().getAttribute("TOKEN_IN_SESSION");

    if (token != null && token.equals(sessionToken)) {
      req.getSession().removeAttribute("TOKEN_IN_SESSION"); // 銷毀
      String money = req.getParameter("money");
      System.out.println("轉出金額:" + money);
      out.print("轉出金額:" + money);
    } else {
      out.print("重複提交或非法請求!");
    }
  }
}

階段二:Servlet 分離產生 Token,JSP 僅渲染

將 Token 產生與轉發邏輯移至 TransferPrepareServlet,JSP 使用 EL 表達式 ${token} 接收,關注點分離更清晰。

@WebServlet("/transfer")
public class TransferPrepareServlet extends HttpServlet {
  protected void service(HttpServletRequest req, HttpServletResponse resp) 
      throws ServletException, IOException {
    String token = UUID.randomUUID().toString();
    req.getSession().setAttribute("TOKEN_IN_SESSION", token);
    req.setAttribute("token", token);
    req.getRequestDispatcher("/views/repeatsubmit/submit.jsp").forward(req, resp);
  }
}
<h3>轉賬界面</h3>
<form action="/trans" method="post">
  <input type="hidden" name="token" value="${token}"/>
  轉賬金額:<input type="text" name="money" min="1" required /><br/>
  <input type="submit" value="轉賬" />
</form>

階段三:抽取 TokenUtil 工具類,複用零負擔

public class TokenUtil {
  private final static String TOKEN_IN_SESSION = "TOKEN_IN_SESSION";

  /** 產生 Token 並存入 Session、Request */
  public static void saveToken(HttpServletRequest req) {
    String token = UUID.randomUUID().toString();
    req.getSession().setAttribute(TOKEN_IN_SESSION, token);
    req.setAttribute("token", token);
  }

  /** 驗證並銷毀 Token,成功回 true */
  public static boolean validateToken(HttpServletRequest req, String tokenInRequest) {
    String sessionToken = (String) req.getSession().getAttribute(TOKEN_IN_SESSION);
    if (tokenInRequest != null && tokenInRequest.equals(sessionToken)) {
      req.getSession().removeAttribute(TOKEN_IN_SESSION);
      return true;
    }
    return false;
  }
}

使用時:

// 表單準備階段
TokenUtil.saveToken(req);

// 提交處理階段
if (TokenUtil.validateToken(req, req.getParameter("token"))) {
  // 合法業務邏輯
} else {
  // 重複提交處理
}

總結

Token 機制以「一次性憑證」確保表單提交的唯一性與時序性。從 JSP 內嵌到 Servlet 分離,再到工具類封裝,層層遞進體現了關注點分離與可複用性的設計原則。實務上建議直接採用階段三的工具類,或整合至攔截器/過濾器實現全域防重,進一步簡化業務代碼。

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