首頁 >後端開發 >Golang >使用 ginvalidator 簡化 Go 中的 Gin 輸入驗證

使用 ginvalidator 簡化 Go 中的 Gin 輸入驗證

Linda Hamilton
Linda Hamilton原創
2024-12-01 11:52:11894瀏覽

Simplify Gin Input Validation in Go with ginvalidator

概述

ginvalidator 是一組 Gin 中間件,它包裝了我的另一個開源包 validatorgo 提供的廣泛的驗證器和消毒器集合。它還使用流行的開源套件 gjson 進行 JSON 欄位語法,提供從 JSON 物件中高效查詢和提取資料的功能。

它允許您以多種方式組合它們,以便您可以驗證和清理您的 Gin 請求,並提供工具來確定請求是否有效,以及根據您的驗證器匹配哪些資料。

它是基於流行的js/express庫express-validator

支援

此版本的 ginvalidator 要求您的應用程式在 Go 1.16 上運行。
它也經過驗證可與 Gin 1.x.x.

搭配使用

基本原理

為什麼不使用?

  • 手寫驗證器: 您可以手動編寫自己的驗證邏輯,但這很快就會變得重複且混亂。每次需要新的驗證時,您都在一遍又一遍地編寫相同類型的程式碼。很容易出錯,維護起來很痛苦。
  • Gin 內建的模型綁定與驗證: Gin 內建了驗證,但它不適合所有人。結構標籤具有限制性,並使您的程式碼更難以閱讀,尤其是當您需要複雜的規則時。另外,驗證與模型連結得太緊密,這不利於靈活性。
  • 其他函式庫(如 Galidator): 還有其他庫,但它們通常感覺對其所做的事情來說過於複雜。它們需要比您預期更多的設定和工作,特別是當您只想要一個簡單、直接的驗證解決方案時。

安裝

確保您的電腦上安裝了 Go。

第 1 步:建立一個新的 Go 模組

  1. 使用您選擇的名稱建立一個空資料夾。
  2. 開啟終端,導航 (cd) 到該資料夾,並初始化一個新的 Go 模組:
go mod init example.com/learning

步驟2:安裝所需的軟體包

使用 go get 安裝必要的軟體包。

  1. 安裝琴酒:
go get -u github.com/gin-gonic/gin
  1. 安裝ginvalidator:
go get -u github.com/bube054/ginvalidator

入門

學習東西的最好方法之一就是透過實例!因此,讓我們捲起袖子,開始編寫程式碼。

設定

首先需要的是運行一個 Gin 伺服器。讓我們實作一個向某人打招呼的功能;為此,創建一個 main.go 然後添加以下程式碼:

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/hello", func(ctx *gin.Context) {
        person := ctx.Query("person")
        ctx.String(http.StatusOK, "Hello, %s!", person)
    })

    r.Run() // listen and serve on 0.0.0.0:8080
}

現在透過在終端機上執行 go run main.go 來運行此檔案。

go mod init example.com/learning

HTTP 伺服器應該正在運行,您可以打開 http://localhost:8080/hello?person=John 向 John 致敬!

提示:
您可以將 Air 與 Go 和 Gin 結合使用來實現即時重新加載。每當文件更改時,這些都會自動重新啟動伺服器,因此您不必自己執行此操作!

新增驗證器

所以伺服器正在工作,但有問題。最值得注意的是,當某人的名字未設定時,您不想向某人打招呼。
例如,造訪 http://localhost:8080/hello 將列印「Hello,」。

這就是 ginvalidator 派上用場的地方。它提供了用於驗證您的請求的驗證器、消毒器和修飾符。
讓我們新增一個驗證器和一個修飾符來檢查人員查詢字串不能為空,驗證器名為 Empty ,修飾符名為 Not:

go get -u github.com/gin-gonic/gin

注意:

為簡潔起見,程式碼範例中使用 gv 作為 ginvalidator 的別名。

現在,重新啟動伺服器,然後再次造訪 http://localhost:8080/hello。嗯,它仍然打印“Hello,!”...為什麼?

處理驗證錯誤

ginvalidator驗證鏈不會自動向使用者報告驗證錯誤。
原因很簡單:當您添加更多驗證器或更多欄位時,您希望如何收集錯誤?您想要一份所有錯誤的列表,每個字段只有一個,整體只有一個......?

所以下一個明顯的步驟是再次更改上面的程式碼,這次使用 ValidationResult 函數驗證驗證結果:

go get -u github.com/bube054/ginvalidator

現在,如果您再次訪問 http://localhost:8080/hello,您將看到以下 JSON 內容,為了清晰起見,我們進行了格式化:

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/hello", func(ctx *gin.Context) {
        person := ctx.Query("person")
        ctx.String(http.StatusOK, "Hello, %s!", person)
    })

    r.Run() // listen and serve on 0.0.0.0:8080
}

現在,這告訴我們的是

  • 此請求中只有一個錯誤;
  • 這個欄位稱為 person;
  • 它位於查詢字串中(位置:「queries」);
  • 給出的錯誤訊息是「無效值」。

這是一個更好的場景,但仍然可以改進。我們繼續吧。

創建更好的錯誤訊息

所有請求位置驗證器都接受可選的第二個參數,它是用來格式化錯誤訊息的函數。如果提供 nil,將使用預設的通用錯誤訊息,如上面的範例所示。

go run main.go

現在,如果您再次造訪 http://localhost:8080/hello,您將看到以下 JSON 內容,並帶有新的錯誤訊息:

package main

import (
    "net/http"

    gv "github.com/bube054/ginvalidator"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/hello", gv.NewQuery("person", nil).
        Chain().
        Not().
        Empty(nil).
        Validate(), func(ctx *gin.Context) {
            person := ctx.Query("person")
            ctx.String(http.StatusOK, "Hello, %s!", person)
        })

    r.Run()
}

存取經過驗證/清理的數據

您可以使用 GetMatchedData,它會自動收集 ginvalidator 已驗證和/或清理的所有資料。然後可以使用 MatchedData 的 Get 方法存取此資料:

go mod init example.com/learning

開 http://localhost:8080/hello?person=John 向 John 致敬!

可用位置有 BodyLocation、CookieLocation、QueryLocation、ParamLocation 和 HeaderLocation。
每個位置都包含一個 String 方法,該方法傳回儲存經過驗證/清理的資料的位置。

淨化輸入

雖然用戶不能再發送空人名,但它仍然可以將 HTML 注入您的頁面!這稱為跨站腳本漏洞 (XSS)。
讓我們看看它是如何工作的。前往 http://localhost:8080/hello?person=John,您應該會看到「Hello, John!」。
雖然此範例很好,但攻擊者可以將人員查詢字串變更為 <script> 。載入自己的 JavaScript 的標籤可能有害。 <br> 在這種情況下,緩解 ginvalidator 問題的一種方法是使用消毒劑,特別是 Escape,它將特殊的 HTML 字元與其他可以表示為文字的字元進行轉換。 <br> </script>

go get -u github.com/gin-gonic/gin

現在,如果您重新啟動伺服器並重新整理頁面,您將看到「Hello, John!」。我們的範例頁面不再容易受到 XSS 攻擊!

⚠️ 註:

ginvalidator 在清理期間不會修改 http.Request 值。若要存取清理後的數據,請務必使用 GetMatchedData 函數。

驗證鏈

驗證鍊是 ginvalidator 中的主要概念之一,因此了解它很有用,以便您可以有效地使用它。

但不用擔心:如果您已經閱讀了入門指南,那麼您已經在不知不覺中使用了驗證鏈!

什麼是驗證鏈?

驗證鍊是使用以下函數建立的,每個函數都針對 HTTP 請求中的特定位置:

  • NewBody:驗證來自 http.Request 正文的資料。它的位置是 BodyLocation。
  • NewCookie:驗證來自 http.Request cookie 的資料。它的位置是 CookieLocation。
  • NewHeader:驗證來自 http.Request 標頭的資料。它的位置是 HeaderLocation。
  • NewParam:驗證 Gin 路由參數中的資料。它的位置是 ParamLocation。
  • NewQuery:驗證來自 http.Request 查詢參數的資料。它的位置是 QueryLocation。

它們之所以有這個名字,是因為它們用驗證(或清理)來包裝字段的值,並且它的每個方法都返回自身。
這種模式通常稱為方法鏈,因此稱為驗證鏈。

驗證鏈不僅具有許多用於定義驗證、清理和修改的有用方法,而且還具有傳回 Gin 中介軟體處理函數的 Validate 方法。

這是驗證鏈通常如何使用以及如何閱讀的範例:

go mod init example.com/learning

特徵

驗證鏈有三種方法:驗證器、消毒器和修飾器。

驗證器決定請求欄位的值是否有效。這表示檢查該欄位是否採用您期望的格式。例如,如果您正在建立註冊表單,您的要求可能是使用者名稱必須是電子郵件地址,並且密碼的長度必須至少為 8 個字元。

如果該值無效,則會使用一些錯誤訊息來記錄該欄位的錯誤。然後可以稍後在 Gin 路由處理程序中檢索此驗證錯誤並將其傳回給使用者。

他們是:

  • 自訂驗證器
  • 包含
  • 等於
  • 阿壩路由
  • 之後
  • 阿爾法
  • 字母數字
  • ASCII
  • Base32
  • Base58
  • Base64
  • 之前
  • 比克
  • 布林值
  • BTC位址
  • 位元組長度
  • 信用卡
  • 貨幣
  • DataURI
  • 日期
  • 十進位
  • 可整除
  • EAN
  • 電子郵件
  • 以太坊地址
  • 漂浮
  • FQDN
  • 貨運貨櫃ID
  • 全寬
  • 半寬
  • 哈希
  • 十六進位
  • 十六進位顏色
  • HSL
  • 國際銀行帳號
  • 身分證
  • IMEI
  • 國際
  • IP
  • IP範圍
  • ISIN
  • ISO4217
  • ISO6346
  • ISO6391
  • ISO8601
  • ISO31661Alpha2
  • ISO31661Alpha3
  • ISO31661 數字
  • ISRC
  • ISSN
  • JSON
  • 拉長
  • 車牌
  • 語言環境
  • 小寫
  • LuhnNumber
  • Mac位址
  • MagnetURI
  • MailtoURI
  • MD5
  • 默劇類型
  • 手機
  • MongoID
  • 多位元組
  • 數字
  • 八進位
  • 護照號碼
  • 港口
  • 郵遞區號
  • RFC3339
  • RGB顏色
  • SemVer
  • 蛞蝓
  • 強密碼
  • 稅號
  • 代理對
  • 時間
  • ULID
  • 大寫
  • 網址
  • UUID
  • 可變寬度
  • 加值稅
  • 已列入白名單
  • 比賽

消毒劑會改變場值。它們對於消除值中的噪音很有用,甚至可能提供一些針對威脅的基本防線。

Sanitizers 將更新後的欄位值保留回 Gin 上下文中,以便其他 ginvalidator 函數、您自己的路由處理程序程式碼,甚至其他中間件都可以使用它。

他們是:

  • 客製化消毒劑
  • 黑名單
  • 逃脫
  • LTrim
  • 標準化電子郵件
  • RTrim
  • 脫衣低
  • 轉布林值
  • 迄今為止
  • 漂浮
  • ToInt
  • 修剪
  • 逃亡
  • 白名單

修飾符定義驗證鏈運行時的行為方式。

他們是:

  • 保釋
  • 如果
  • 不是
  • 跳過
  • 可選

注意:

這些方法在 pkg.go.dev ginvalidator 文件中使用 GoDoc 進行了完整記錄。如果有任何細節不清楚,您可能還需要參考 validatorgo 套件中的相關函數以獲取更多上下文,我將在下面進行解釋。

標準驗證器/消毒器

驗證鏈公開的所有功能實際上都來自 validatorgo,這是我的其他開源 go 包之一,專門從事字串驗證/清理。請檢查一下,加星號並分享? ? ? ,謝謝。

這包括所有 validatorgo 驗證器和清理程序,從常用的 IsEmail、IsLength 和 Trim 到更小眾的 IsISBN、IsMultibyte 和 StripLow!

這些在 ginvalidator 被稱為標準驗證器和標準消毒器。但沒有來自 validatorgo 的 Is 前綴。

連結順序

在驗證鏈上呼叫方法的順序通常很重要。
它們幾乎總是按照指定的順序運行,因此您只需閱讀驗證鏈的定義(從第一個連結方法到最後一個方法)就可以知道驗證鏈將做什麼。

以下面的程式碼片段為例:

go mod init example.com/learning

在這種情況下,如果使用者傳遞一個僅由空格組成的「search_query」值,它不會為空,因此驗證通過。但由於 .Trim() 消毒劑存在,空格將被刪除,並且該字段將變為空,因此您實際上最終會得到誤報。

現在,將其與以下程式碼片段進行比較:

go get -u github.com/gin-gonic/gin

這條鏈將更明智地刪除空格,然後驗證值是否不為空。

此規則的一個例外是 .Optional():它可以放置在鏈中的任何一點,並將該鏈標記為可選。

重複使用驗證鏈

如果您希望重複使用同一個鏈,最好從函數中傳回它們:

go get -u github.com/bube054/ginvalidator

領域選擇

在 ginvalidator 中,欄位是任何經過驗證或清理的值,並且它是字串。

幾乎每個函數或值都以某種方式由 ginvalidator 引用欄位傳回。因此,了解欄位路徑語法對於選擇驗證欄位以及存取驗證錯誤或驗證資料時非常重要。

句法

  • 正文欄位僅對以下內容類型有效:

    • application/json:這使用 GJSON 路徑語法來提取值。詳細資訊請參閱連結文件。
    • 範例
    go mod init example.com/learning
    

    使用路徑 user.name,擷取的值為「John」。

    • application/x-www-form-urlencoded:通常用於 HTML 表單提交。字段在正文中作為鍵值對提交。
    • 範例
    go get -u github.com/gin-gonic/gin
    

    身體:

    go get -u github.com/bube054/ginvalidator
    

    欄位“name”的值為“John”,“email”的值為“john.doe@example.com”。

    • multipart/form-data:常用於檔案上傳或隨檔案提交表單資料時。
    • 範例
    package main
    
    import (
        "net/http"
    
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        r := gin.Default()
    
        r.GET("/hello", func(ctx *gin.Context) {
            person := ctx.Query("person")
            ctx.String(http.StatusOK, "Hello, %s!", person)
        })
    
        r.Run() // listen and serve on 0.0.0.0:8080
    }
    

    身體:

    go run main.go
    

    欄位“name”的值為“John”,“file”為上傳的檔案。

  • 查詢欄位對應URL搜尋參數,其值自動為Gin未轉義的url。

    範例:

    • 欄位:“姓名”,值:“約翰”
    package main
    
    import (
        "net/http"
    
        gv "github.com/bube054/ginvalidator"
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        r := gin.Default()
    
        r.GET("/hello", gv.NewQuery("person", nil).
            Chain().
            Not().
            Empty(nil).
            Validate(), func(ctx *gin.Context) {
                person := ctx.Query("person")
                ctx.String(http.StatusOK, "Hello, %s!", person)
            })
    
        r.Run()
    }
    
    • 欄位:“full_name”,值:“John Doe”
    package main
    
    import (
        "net/http"
    
        gv "github.com/bube054/ginvalidator"
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        r := gin.Default()
    
        r.GET("/hello",
            gv.NewQuery("person", nil).
                Chain().
                Not().
                Empty(nil).
                Validate(),
            func(ctx *gin.Context) {
                result, err := gv.ValidationResult(ctx)
                if err != nil {
                    ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
                        "message": "The server encountered an unexpected error.",
                    })
                    return
                }
    
                if len(result) != 0 {
                    ctx.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
                        "errors": result,
                    })
                    return
                }
    
                person := ctx.Query("person")
                ctx.String(http.StatusOK, "Hello, %s!", person)
            })
    
        r.Run()
    }
    
  • Param 欄位 表示 URL 路徑參數,其值會被 ginvalidator 自動轉義。

    範例:

    • 欄位:“id”,值:“123”
    {
      "errors": [
        {
          "location": "queries",
          "message": "Invalid value",
          "field": "person",
          "value": ""
        }
      ]
    }
    
  • 標頭欄位是HTTP請求標頭,它們的值不是未轉義的。如果您提供非規範標頭金鑰,將會出現日誌警告。

    範例:

    • 欄位:“用戶代理”,值:“Mozilla/5.0”
    package main
    
    import (
        "net/http"
    
        gv "github.com/bube054/ginvalidator"
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        r := gin.Default()
    
        r.GET("/hello",
            gv.NewQuery("person",
                func(initialValue, sanitizedValue, validatorName string) string {
                    return "Please enter your name."
                },
            ).Chain().
                Not().
                Empty(nil).
                Validate(),
            func(ctx *gin.Context) {
                result, err := gv.ValidationResult(ctx)
                if err != nil {
                    ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
                        "message": "The server encountered an unexpected error.",
                    })
                    return
                }
    
                if len(result) != 0 {
                    ctx.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
                        "errors": result,
                    })
                    return
                }
    
                person := ctx.Query("person")
                ctx.String(http.StatusOK, "Hello, %s!", person)
            })
    
        r.Run()
    }
    
  • Cookie 欄位 是 HTTP cookie,其值自動為 Gin 未轉義的 url。

    範例:

    • 欄位:“session_id”,值:“abc 123”
    {
      "errors": [
        {
          "location": "queries",
          "message": "Please enter your name.",
          "field": "person",
          "value": ""
        }
      ]
    }
    

自訂快速驗證器

如果您正在建置的伺服器不是一個非常簡單的伺服器,那麼遲早您將需要驗證器、清理器和錯誤訊息,而不僅僅是 ginvalidator 內建的內容。

自訂驗證器和消毒器

ginvalidator 無法滿足您並且您可能會遇到的一個經典需求是在使用者註冊時驗證電子郵件地址是否正在使用。

可以透過實作自訂驗證器在 ginvalidator 中執行此操作。

CustomValidator 是驗證鏈上可用的方法,它接收特殊函數 CustomValidatorFunc,並且必須傳回一個布林值來確定欄位是否有效。

CustomSanitizer 也是驗證鏈上可用的方法,它接收特殊函數 CustomSanitizerFunc,並且必須傳回新的清理值。

實作自訂驗證器

CustomValidator 可以透過使用 goroutine 和sync.WaitGroup 來非同步處理並發操作。在驗證器中,您可以為每個非同步任務啟動 goroutine,並將每個任務新增至 WaitGroup。所有任務完成後,驗證器應傳回一個布林值。

例如,為了檢查電子郵件是否未被使用:

go mod init example.com/learning

或您也可以驗證密碼是否與重複符合:

go get -u github.com/gin-gonic/gin

⚠️ 註:
如果請求正文將被多次存取(無論是在同一驗證鏈中、在同一請求上下文的另一個驗證鏈中還是在後續處理程序中),請確保在每次讀取後重置請求正文。如果不這樣做,再次讀取正文時可能會導致錯誤或資料遺失。

實施自訂消毒劑

CustomSanitizer 沒有太多規則。無論它們傳回什麼值,都是該欄位將取得的新值。
自訂清理程序也可以透過使用 goroutine 和sync.WaitGroup 來非同步處理並發操作。

go get -u github.com/bube054/ginvalidator

錯誤訊息

每當欄位值無效時,都會為其記錄錯誤訊息。
預設錯誤訊息是“無效值”,它根本無法描述錯誤的內容,因此您可能需要對其進行自訂。您可以透過
進行客製化

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()

    r.GET("/hello", func(ctx *gin.Context) {
        person := ctx.Query("person")
        ctx.String(http.StatusOK, "Hello, %s!", person)
    })

    r.Run() // listen and serve on 0.0.0.0:8080
}
  • 初始值是從請求中提取的原始值(在任何清理之前)。
  • sanitizedValue 是經過清理後的值(如果適用)。
  • validatorName 是失敗的驗證器的名稱,有助於識別未通過的驗證規則。

有關驗證器名稱的完整列表,請參閱 ginvalidator 常數。

維護者

  • bube054 - Attah Gbubemi David(作者)

以上是使用 ginvalidator 簡化 Go 中的 Gin 輸入驗證的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn