防止表單重複提交的4種方法
1.背景與介紹:
平時開發的專案中可能會出現下面這些情況:
- 由於使用者誤操作,多次點選表單提交按鈕。
- 由於網速等原因造成頁面卡頓,使用者重複重新整理提交頁面。
- 黑客或惡意使用者使用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自定義切入實現
實現原理:
- 自定義防止重複提交標記(@AvoidRepeatableCommit)。
- 對需要防止重複提交的Congtroller裡的mapping方法加上該註解。
- 新增Aspect切入點,為@AvoidRepeatableCommit加入切入點。
- 每次提交表單時,Aspect都會儲存當前key到reids(須設定過期時間)。
- 重複提交時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是否一致,當不一致時,被認為是重複提交,將不准許通過。 整個流程可以由如下