Struts的Token(令牌)機制能夠很好的解決表單重復提交的問題
基本原理是
服務器端在處理到達的請求之前
會將請求中包含的令牌值與保存在當前用戶會話中的令牌值進行比較
看是否匹配
在處理完該請求後
且在答復發送給客戶端之前
將會產生一個新的令牌
該令牌除傳給客戶端以外
也會將用戶會話中保存的舊的令牌進行替換
這樣如果用戶回退到剛才的提交頁面並再次提交的話
客戶端傳過來的令牌就和服務器端的令牌不一致
從而有效地防止了重復提交的發生
這時其實也就是兩點
第一
你需要在請求中有這個令牌值
請求中的令牌值如何保存
其實就和我們平時在頁面中保存一些信息是一樣的
通過隱藏字段來保存
保存的形式如
〈input type=
hidden
name=
org
apacl
TOKEN
value=
aa
f
fd
c
c
c
ae
〉
這個value是TokenProcessor類中的generateToken()獲得的
是根據當前用戶的session id和當前時間的long值來計算的
第二
在客戶端提交後
我們要根據判斷在請求中包含的值是否和服務器的令牌一致
因為服務器每次提交都會生成新的Token
所以
如果是重復提交
客戶端的Token值和服務器端的Token值就會不一致
下面就以在數據庫中插入一條數據來說明如何防止重復提交
在Action中的add方法中
我們需要將Token值明確的要求保存在頁面中
只需增加一條語句
saveToken(request);
如下所示
public ActionForward add(ActionMapping mapping
ActionForm form
HttpServletRequest request
HttpServletResponse response)
//前面的處理省略
saveToken(request);
return mapping
findForward(
add
);
}在Action的insert方法中
我們根據表單中的Token值與服務器端的Token值比較
如下所示
public ActionForward insert(ActionMapping mapping
ActionForm form
HttpServletRequest request
HttpServletResponse response)
if (isTokenValid(request
true)) {
// 表單不是重復提交
//這裡是保存數據的代碼
} else {
//表單重復提交
saveToken(request);
//其它的處理代碼
}
}
其實使用起來很簡單
舉個最簡單
最需要使用這個的例子
一般控制重復提交主要是用在對數據庫操作的控制上
比如插入
更新
刪除等
由於更新
刪除一般都是通過id來操作(例如
updateXXXById
removeXXXById)
所以這類操作控制的意義不是很大(不排除個別現象)
重復提交的控制也就主要是在插入時的控制了
先說一下
我們目前所做項目的情況
目前的項目是用Struts+Spring+Ibatis
頁面用jstl
Struts復雜View層
Spring在Service層提供事務控制
Ibatis是用來代替JDBC
所有頁面的訪問都不是直接訪問jsp
而是訪問Structs的Action
再由Action來Forward到一個Jsp
所有針對數據庫的操作
比如取數據或修改數據
都是在Action裡面完成
所有的Action一般都繼承BaseDispatchAction
這個是自己建立的類
目的是為所有的Action做一些統一的控制
在Struts層
對於一個功能
我們一般分為兩個Action
一個Action裡的功能是不需要調用Struts的驗證功能的(常見的方法名稱有add
edit
remove
view
list)
另一個是需要調用Struts的驗證功能的(常見的方法名稱有insert
update)
就拿論壇發貼來說吧
論壇發貼首先需要跳轉到一個頁面
你可以填寫帖子的主題和內容
填寫完後
單擊
提交
貼子就發表了
所以這裡經過兩個步驟
轉到一個新增的頁面
在Action裡我們一般稱為add
例如
public ActionForward add(ActionMapping mapping
ActionForm form
HttpServletRequest request
HttpServletResponse response)
throws Exception {
//這一句是輸出調試信息
表示代碼執行到這一段了
log
debug(
:: action
subject add
);
//your code here
//這裡保存Token值
saveToken(request);
//跳轉到add頁面
在Structs
config
xml裡面定義
例如
跳轉到subjectAdd
jsp
return mapping
findForward(
add
);
}
在填寫標題和內容後
選擇 提交
會提交到insert方法
在insert方法裡判斷
是否重復提交了
public ActionForward insert(ActionMapping mapping
ActionForm form
HttpServletRequest request
HttpServletResponse response){
if (isTokenValid(request
true)) {
// 表單不是重復提交
//這裡是保存數據的代碼
} else {
//表單重復提交
saveToken(request);
//其它的處理代碼
}
}
下面更詳細一點(注意
下面所有的代碼使用全角括號)
你想發貼時
點擊
我要發貼
鏈接的代碼可以裡這樣的
〈html:link action=
subject
do?method=add
〉我要發貼〈/html:link〉
subject
do 和 method 這些在struct
config
xml如何定義我就不說了
點擊鏈接後
會執行subject
do的add方法
代碼如上面說的
跳轉到subjectAdd
jsp頁面
頁面的代碼大概如下
〈html:form action=
subjectForm
do?method=insert
〉
〈html:text property=
title
/〉
〈html:textarea property=
content
/〉
〈html:submit property=
發表
/〉
〈html:reset property=
重填
/〉
〈html:form〉
如果你在add方法裡加了
saveToken(request);
這一句
那在subjectAdd
jsp生成的頁面上
會多一個隱藏字段
類似於這樣〈input type=
hidden
name=
org
apacl
TOKEN
value=
aa
f
fd
c
c
c
ae
〉
點擊發表後
表單提交到subjectForm
do裡的insert方法後
你在insert方法裡要將表單的數據插入到數據庫中
如果沒有進行重復提交的控制
那麼每點擊一次浏覽器的刷新按鈕
都會在數據庫中插入一條相同的記錄
增加下面的代碼
你就可以控制用戶的重復提交了
if (isTokenValid(request
true)) {
// 表單不是重復提交
//這裡是保存數據的代碼
} else {
//表單重復提交
saveToken(request);
//其它的處理代碼
}
注意
你必須在add方法裡使用了saveToken(request)
你才能在insert裡判斷
否則
你每次保存操作都是重復提交
記住一點
Struts在你每次訪問Action的時候
都會產生一個令牌
保存在你的Session裡面
如果你在Action裡的函數裡面
使用了saveToken(request);
那麼這個令牌也會保存在這個Action所Forward到的jsp所生成的靜態頁面裡
如果你在你Action的方法裡使用了isTokenValid
那麼Struts會將你從你的request裡面去獲取這個令牌值
然後和Session裡的令牌值做比較
如果兩者相等
就不是重復提交
如果不相等
就是重復提交了
由於我們項目的所有Action都是繼承自BaseDispatchAction這個類
所以我們基本上都是在這個類裡面做了表單重復提交的控制
默認是控制add方法和insert方法
如果需要控制其它的方法
就自己手動寫上面這些代碼
否則是不需要手寫的
控制的代碼如下
public abstract class BaseDispatchAction extends BaseAction {
protected ActionForward perform(ActionMapping mapping
ActionForm form
HttpServletRequest request
HttpServletResponse response)
throws Exception {
String parameter = mapping
getParameter();
String name = request
getParameter(parameter);
if (null == name) { //如果沒有指定 method
則默認為 list
name =
list
;
}
if (
add
equals(name)) {
if (
add
equals(name)) {
saveToken(request);
}
} else if (
insert
equals(name)) {
if (!isTokenValid(request
true)) {
resetToken(request);
saveError(request
new ActionMessage(
error
repeatSubmit
));
log
error(
重復提交!
);
return mapping
findForward(
error
);
}
}
return dispatchMethod
(mapping
form
request
response
name);
}
}
From:http://tw.wingwit.com/Article/program/Java/ky/201311/28119.html