不知道你是否曾經看到過一個論壇或者博客,在一個帖子或者文章后面出現多條重復的記錄,這些大多數是因為用戶重復遞交了留言的表單引起的。由于種種原因,用戶經常會重復遞交表單。通常這只是鼠標的誤操作,如雙擊了遞交按鈕,也可能是為了編輯或者再次核對填寫過的信息,點擊了瀏覽器的后退按鈕,然后又再次點擊了遞交按鈕而不是瀏覽器的前進按鈕。當然,也可能是故意的——比如,在某項在線調查或者博彩活動中重復投票。那我們如何有效的防止用戶多次遞交相同的表單呢?
解決方案是在表單中添加一個帶有唯一值的隱藏字段。在驗證表單時,先檢查帶有該惟一值的表單是否已經遞交過了。如果是,拒絕再次遞交;如果不是,則處理表單進行邏輯處理。另外,如果是采用了Ajax模式遞交表單的話,當表單遞交后,通過javascript來禁用表單的遞交按鈕。
我繼續拿4.2小節的例子優化:
~~~
<input type="checkbox" name="interest" value="football">足球
<input type="checkbox" name="interest" value="basketball">籃球
<input type="checkbox" name="interest" value="tennis">網球
用戶名:<input type="text" name="username">
密碼:<input type="password" name="password">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="登陸">
~~~
我們在模版里面增加了一個隱藏字段`token`,這個值我們通過MD5(時間戳)來獲取惟一值,然后我們把這個值存儲到服務器端(session來控制,我們將在第六章講解如何保存),以方便表單提交時比對判定。
~~~
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"))) //輸出到客戶端
}
}
~~~
上面的代碼輸出到頁面的源碼如下:
[](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/images/4.4.token.png?raw=true)
圖4.4 增加token之后在客戶端輸出的源碼信息
我們看到token已經有輸出值,你可以不斷的刷新,可以看到這個值在不斷的變化。這樣就保證了每次顯示form表單的時候都是唯一的,用戶遞交的表單保持了唯一性。
我們的解決方案可以防止非惡意的攻擊,并能使惡意用戶暫時不知所措,然后,它卻不能排除所有的欺騙性的動機,對此類情況還需要更復雜的工作。
- 第一章 Go環境配置
- 1.1 Go安裝
- 1.2 GOPATH 與工作空間
- 1.3 Go 命令
- 1.4 Go開發工具
- 1.5 小結
- 第二章 Go語言基礎
- 2.1 你好,Go
- 2.2 Go基礎
- 2.3 流程和函數
- 2.4 struct類型
- 2.5 面向對象
- 2.6 interface
- 2.7 并發
- 2.8 總結
- 第三章 Web基礎
- 3.1 Web工作方式
- 3.2 Go搭建一個Web服務器
- 3.3 Go如何使得Web工作
- 3.4 Go的http包詳解
- 3.5 小結
- 第四章 表單
- 4.1 處理表單的輸入
- 4.2 驗證表單的輸入
- 4.3 預防跨站腳本
- 4.4 防止多次遞交表單
- 4.5 處理文件上傳
- 4.6 小結
- 第五章 訪問數據庫
- 5.1 database/sql接口
- 5.2 使用MySQL數據庫
- 5.3 使用SQLite數據庫
- 5.4 使用PostgreSQL數據庫
- 5.5 使用beedb庫進行ORM開發
- 5.6 NOSQL數據庫操作
- 5.7 小結
- 第六章 session和數據存儲
- 6.1 session和cookie
- 6.2 Go如何使用session
- 6.3 session存儲
- 6.4 預防session劫持
- 6.5 小結
- 第七章 文本處理
- 7.1 XML處理
- 7.2 JSON處理
- 7.3 正則處理
- 7.4 模板處理
- 7.5 文件操作
- 7.6 字符串處理
- 7.7 小結
- 第八章 Web服務
- 8.1 Socket編程
- 8.2 WebSocket
- 8.3 REST
- 8.4 RPC
- 8.5 小結
- 第九章 安全與加密
- 9.1 預防CSRF攻擊
- 9.2 確保輸入過濾
- 9.3 避免XSS攻擊
- 9.4 避免SQL注入
- 9.5 存儲密碼
- 9.6 加密和解密數據
- 9.7 小結
- 第十章 國際化和本地化
- 10.1 設置默認地區
- 10.2 本地化資源
- 10.3 國際化站點
- 10.4 小結
- 第十一章 錯誤處理,調試和測試
- 11.1 錯誤處理
- 11.2 使用GDB調試
- 11.3 Go怎么寫測試用例
- 11.4 小結
- 第十二章 部署與維護
- 12.1 應用日志
- 12.2 網站錯誤處理
- 12.3 應用部署
- 12.4 備份和恢復
- 12.5 小結
- 第十三章 如何設計一個Web框架
- 13.1 項目規劃
- 13.2 自定義路由器設計
- 13.3 controller設計
- 13.4 日志和配置設計
- 13.5 實現博客的增刪改
- 13.6 小結
- 第十四章 擴展Web框架
- 14.1 靜態文件支持
- 14.2 Session支持
- 14.3 表單及驗證支持
- 14.4 用戶認證
- 14.5 多語言支持
- 14.6 pprof支持
- 14.7 小結
- 附錄A 參考資料