Web系统的表单重复提交问题及解决方案

发表于:2020-9-25 09:30

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:昆梧    来源:博客园

分享:
  表单页面JSP:
   <%@ page contentType="text/html;charset=UTF-8" language="java" %>
   <html>
     <head>
       <title>表单页面</title>
     </head>
     <body>
       <form action="commit" method="post">
         <input type="text" name="username" />
         <input type="submit" id="submit"/>
       </form>
     </body>
   </html>
  表单处理Servlet
public class FormServlet extends HttpServlet {
       @Override
       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
           try {
               Thread.sleep(2000); // 模拟网络延迟
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           req.setCharacterEncoding("utf-8");
           System.out.println("对" + req.getParameter("username") + "进行处理");
       }
   }

  用户重复提交的场景
  只列举了常见的场景
  场景一:表单提交后,因为网络延迟,让用户有时间重复点击提交。
  场景二:表单提交后,用户刷新页面,导致表单重复提交。
  场景三:表单提交后,用户退回上一个页面,再次点击提交。
  场景四:在一个浏览器中,用户打开两个标签页进行提交。
  重复提交的解决方案
  2.1 方案一 
  在前端,通过设置一个标识变量,标识表单的提交状态。(只能解决场景一)
  标识变量默认为false,一旦表单提交触发,会判断标识变量是否为false,如果为false则发送请求并且将标识变量更改为true,如果为true则不发送请求。
   <%@ page contentType="text/html;charset=UTF-8" language="java" %>
   <html>
     <head>
       <title>表单页面</title>
       <script type="text/javascript">
         var isCommitted = false; // 默认未提交
         function doSubmit() {
           if (isCommitted == false) {
             isCommitted = true; // 改为已提交
             return true;
           }else {
             return false;
           }
         }
       </script>
     </head>
     <body>
       <form action="commit" method="post" onsubmit="return doSubmit()"> <!--通过doSubmit函数,动态地给onsubmit属性赋值-->
         <input type="text" name="username" />
         <input type="submit" id="submit"/>
       </form>
     </body>
   </html>
  2.2 方案二 
  在后端,通过session和唯一Token来判断重复提交。(可以解决四个场景)
  服务器通过session为用户保存一个唯一Token,并且给到浏览器,浏览器会将其保存在一个隐藏的域中随表单提交。
  当第一次提交时,提交的Token与session中的Token相等,进行相应处理,并且删除session中的Token。   
  1.TokenProcessor的工具类
   public class TokenProcessor {
       private static final TokenProcessor instance = new TokenProcessor();
   
       public static TokenProcessor getInstance() {
           return instance;
       }
       public String makeToken() {
           String token = String.valueOf(System.currentTimeMillis() + new Random().nextInt(999999999));
           try {
               MessageDigest md = MessageDigest.getInstance("md5");
               byte md5[] = md.digest(token.getBytes());
               Base64.Encoder encoder = Base64.getEncoder();
               return encoder.encodeToString(md5);
           } catch (NoSuchAlgorithmException e) {
               e.printStackTrace();
               return null;
           }
       }
   }
  2.获取session和Token的Servlet
   public class GetSessionServlet extends HttpServlet {
       @Override
       protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
           String token = TokenProcessor.getInstance().makeToken();
           System.out.println(token); // 打印Token的值
           req.getSession(true).setAttribute("token", token);
           req.getRequestDispatcher("/index.jsp").forward(req, resp);
       }
   }
  3.表单页面JSP
   <%@ page contentType="text/html;charset=UTF-8" language="java" %>
   <html>
     <head>
       <title>表单页面</title>
     </head>
     <body>
       <form action="commit" method="post">
         <input type="text" name="username" />
         <input type="hidden" name="token" value=${sessionScope.token} />
         <input type="submit" id="submit"/>
       </form>
     </body>
   </html>
  4.表单处理Servlet
   public class FormServlet extends HttpServlet {
       @Override
       protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
           try {
               Thread.sleep(2000); // 模拟网络延迟
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           req.setCharacterEncoding("utf-8");
           HttpSession session = req.getSession(false);
           if (session == null) {
               System.out.println("未拥有session,不处理");
               return;
           }
           String token = (String)session.getAttribute("token");
           if (token == null) {
               System.out.println("session中未拥有Token,不处理");
               return;
           }
           if (req.getParameter("token") == null) {
               System.out.println("浏览器Token为空,不处理");
           }
           if (!token.equals(req.getParameter("token"))) {
               System.out.println("Token不一致,不处理");
               return;
           }
           session.removeAttribute("token");
           System.out.println("对" + req.getParameter("username") + "进行处理");
       }
   }
  以下为方案二测试结果:
  场景一
  场景二
  场景三
  场景四(只有后进入的表单页面有最新的Token)

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理   
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号