基於gin的golang web開發:永遠不要相信用戶的輸入

作爲後端開發者我們要記住一句話:“永遠不要相信用戶的輸入”,這裏所說的用戶可能是人,也可能是另一個應用程序。“永遠不要相信用戶的輸入”是安全編碼的準則,也就是說,任何輸入的內容在驗證無害之前都是有害的。很多應用程序的安全漏洞都和用戶輸入有關,比如SQL注入漏洞。

我們可以通過參數驗證、sql語句過濾和參數化查詢等方式對用戶的輸入進行處理來規避這種安全隱患。本文介紹第一種方法,並對基於gin的golang web開發:模型驗證進行補充,瞭解更多的參數驗證方法。

驗證非必填的郵箱字段

需求是這樣的:我們需要驗證一個字段可以爲空,同時字段的值爲合法的電子郵箱。

type MailRequest struct {
	Email    string `json:"email" binding:"email"` // 郵箱地址
}

代碼的執行結果和我們想的不太一樣,我們沒有爲字段設置required標籤,但是傳入空字符串時會提示Email必須是一個有效的郵箱,解決方法是加入omitempty驗證規則。omitempty允許條件驗證,在沒有爲字段設置值的情況下,跳過後面的驗證規則。注意omitempty要放在其他規則前面。下面是修改後的代碼:

type MailRequest struct {
	Email    string `json:"email" binding:"omitempty,email"` // 郵箱地址
}

驗證0值

先看代碼

type AddRoleRequest struct {
	Available int `json:"available" binding:"required"` // 是否可用 0 不可用 1 可用
}

Available 字段爲int類型,添加了required驗證規則,0爲一個有效的值。Available爲0時不能通過Gin的參數驗證。這裏只需要把字段類型修改爲*int即可。

type AddRoleRequest struct {
	Available *int `json:"available" binding:"required"` // 是否可用 0 不可用 1 可用
}

自定義錯誤消息

前文基於gin的golang web開發:模型驗證結尾部分,我們沒有把參數驗證的錯誤消息完全翻譯成中文,字段名還是英文的。顯然還有更優雅的做法,給用戶提示一個更友好的錯誤信息。

{
    "error": "Username爲必填字段;"
}

返回值中的Username爲字段名稱,可以通過自定義標籤的方式修改錯誤信息中的字段名。我們自定義一個display標籤,然後使用標籤的值替換掉驗證器中的字段。

func init() {
	translator := zh.New()
	uni = ut.New(translator, translator)
	trans, _ = uni.GetTranslator("zh")
	validate := binding.Validator.Engine().(*validator.Validate)
	// 注意這裏
	validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
		return fld.Tag.Get("display")
	})
	_ = zh_translations.RegisterDefaultTranslations(validate, trans)
}

func Translate(err error) string {
	var result string

	errors := err.(validator.ValidationErrors)

	for _, err := range errors {
		errMessage := err.Translate(trans)
		result += errMessage + ";"
	}
	return result[:len(result)-1] // <--
}

type AddUserRequest struct {
    Username string `json:"username" binding:"required" display:"用戶名"`
	Password string `json:"password" binding:"required" display:"密碼"` // 登錄密碼
	Nickname string `json:"nickname" binding:"required" display:"暱稱"` // 暱稱
}

注意代碼中validate.RegisterTagNameFunc方法註冊display標籤,Translate方法也有一些改進,去掉了結果中最後一個分號。

舉個栗子

func checkUser(user string, password string) bool {
	db := GetDbContext()
	defer db.Close()

	dataSql := `
select count(1) from sys_user
where username = '` + user + `' and password = '` + password + `'`
	count := 0
	log.Println(dataSql)
	db.QueryRow(dataSql).Scan(&count)
	return count > 0
}

這段代碼用於判斷賬號密碼是否正確,但是沒有驗證用戶輸入的user和password參數,惡意用戶構造一個特殊的密碼1' or '1'='1dataSql中拼接的sql語句變爲

select count(1) from sys_user
where username = 'xxx' and password = '1' or '1'='1'

查詢結果大於0,方法返回真。這就造成了sql注入。我們可以在password字段上增加規則alphanum驗證字段內容只能爲字母或數字。1' or '1'='1不能通過參數驗證也就規避掉了SQL注入的問題。例子中拼接SQL語句是爲了方便演示,正式項目中不推薦這種寫法。完整代碼如下:

type UserAndPassword struct {
	User string `json:"user" binding:"required,alphanum"`
	Pwd  string `json:"pwd" binding:"required,alphanum"`
}

func IsLoginIn(c *gin.Context) {
	var req = UserAndPassword{}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.String(http.StatusOK, strconv.FormatBool(checkUser(req.User, req.Pwd)))
}

func checkUser(user string, password string) bool {
	db := GetDbContext()
	defer db.Close()

	dataSql := `
select count(1) from sys_user
where username = '` + user + `' and password = '` + password + `'`
	count := 0
	log.Println(dataSql)
	db.QueryRow(dataSql).Scan(&count)
	return count > 0
}

文章出處:基於gin的golang web開發:永遠不要相信用戶的輸入

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章