# 4.4 防止多次提交表單 不知道你是否曾經看到過一個論壇或者部落格,在一個帖子或者文章後面出現多條重複的記錄,這些大多數是因為使用者重複提交了留言的表單引起的。由於種種原因,使用者經常會重複提交表單。通常這只是滑鼠的誤操作,如雙擊了提交按鈕,也可能是為了編輯或者再次核對填寫過的資訊,點選了瀏覽器的後退按鈕,然後又再次點選了提交按鈕而不是瀏覽器的前進按鈕。當然,也可能是故意的——比如,在某項線上調查或者博彩活動中重複投票。那我們如何有效的防止使用者多次提交相同的表單呢? 解決方案是在表單中新增一個帶有唯一值的隱藏欄位。在驗證表單時,先檢查帶有該唯一值的表單是否已經提交過了。如果是,拒絕再次提交;如果不是,則處理表單進行邏輯處理。另外,如果是採用了 Ajax 模式提交表單的話,當表單提交後,透過 javascript 來禁用表單的提交按鈕。 我繼續拿 4.2 小節的例子優化: ```html 足球 籃球 網球 使用者名稱: 密碼: ``` 我們在模版裡面增加了一個隱藏欄位`token`,這個值我們透過 MD5(時戳) 來取得唯一值,然後我們把這個值儲存到伺服器端(session 來控制,我們將在第六章講解如何儲存),以方便表單提交時比對判定。 ```Go func login(w http.ResponseWriter, r *http.Request) { fmt.Println("method:", r.Method) //取得請求的方法 if r.Method == "GET" { crutime := time.Now().Unix() h := md5.New() io.WriteString(h, strconv.FormatInt(crutime, 10)) token := fmt.Sprintf("%x", h.Sum(nil)) t, _ := template.ParseFiles("login.gtpl") t.Execute(w, token) } else { //請求的是登陸資料,那麼執行登陸的邏輯判斷 r.ParseForm() token := r.Form.Get("token") if token != "" { //驗證 token 的合法性 } else { //不存在 token 報錯 } fmt.Println("username length:", len(r.Form["username"][0])) fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //輸出到伺服器端 fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) template.HTMLEscape(w, []byte(r.Form.Get("username"))) //輸出到客戶端 } } ``` 上面的程式碼輸出到頁面的原始碼如下: ![](images/4.4.token.png) 圖 4.4 增加 token 之後在客戶端輸出的原始碼資訊 我們看到 token 已經有輸出值,你可以不斷的重新整理,可以看到這個值在不斷的變化。這樣就保證了每次顯示 form 表單的時候都是唯一的,使用者提交的表單保持了唯一性。 我們的解決方案可以防止非惡意的攻擊,並能使惡意使用者暫時不知所措,然後,它卻不能排除所有的欺騙性的動機,對此類別情況還需要更復雜的工作。 ## links * [目錄]() * 上一節:[預防跨站指令碼](<04.3.md>) * 下一節:[處理檔案上傳](<04.5.md>)