本文正在参加金石计划附加挑战赛------第 二 期命题
1 简介
输入验证对于任何软件应用程序中的数据完整性和安全性都至关重要。 Go 是一种功能强大且流行的编程语言,以其简单性和性能而闻名,它提供"validator"包 (v10) 以实现高效验证。
合理使用验证器可以增强应用程序健壮性。 并且使用输入和输出验证也有助于提高服务安全和用户使用体验。
2 工具和库Validator
Validator.v10这可以帮助更简便快捷地实现参数校验和安全传递变量。
框架gin内置了 github.com/go-playground/validator/v10,用于字段验证。 可以自定义校验规则,满足复杂需求。我们可以灵活地创建针对您的特定应用程序要求定制的自定义验证器。 自定义验证器允许您对数据执行特定于域的规则,从而确保更精确的输入验证。
以下是创建名为 PercentageValidator 的自定义验证程序的示例
3 示例:标签校验
go
import (
"github.com/go-playground/validator/v10"
"github.com/gin-gonic/gin"
)
type User struct {
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0,lte=120"` // 年龄范围 0-120
}
var validate *validator.Validate
func main() {
validate = validator.New()
r := gin.Default()
r.POST("/validate", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Validation passed!"})
})
r.Run()
}
将验证接收 JSON 格式数据的简单 API 终端节点的输入参数。 在此示例中,该函数检查输入值是否表示有效百分比(范围从 0 到 120)。 Validator验证器支持各种数据类型,包括 strings、integers 和 floats
4 更复杂的校验
以下为使用步骤
- 1 使用自定义验证器
您需要在执行输入验证之前将其注册到 Validator V10。
您可以在合适的位置通过以下方式注册它:
go
validate := validator.New()
// Registering the custom validator
validate.RegisterValidation("rangeAge", ageValidator)
- 2 使用校验函数
检测输入的数字是否在100以内
css
func ageValidator(fl validator.FieldLevel) bool {
maxPercent := decimal.NewFromInt(1)
minPercent := decimal.NewFromInt(0)
quantity := decimal.NewFromInt(100)
switch v := fl.Field(); v.Kind() {
case reflect.String:
val, err := decimal.NewFromString(v.String())
logs.Println("val before /100 :", val)
val = val.Div(quantity) // / 100.0
logs.Println("val after /100 :", val)
if err == nil && val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
return true
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val := decimal.NewFromInt(v.Int())
logs.Println("val before /100 :", val)
val = val.Div(quantity)
logs.Println("val after /100 :", val)
if val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
return true
}
case reflect.Float32, reflect.Float64:
val := decimal.NewFromFloat(v.Float())
logs.Println("val before /100 :", val)
val = val.Div(quantity)
logs.Println("val after /100 :", val)
if val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
return true
}
default:
return false
}
return false
}
- 3 绑定到定义数据结构:
接下来,我们定义一个 Go 结构体,它表示我们期望接收的输入的数据结构:
c
type User struct {
Username string `json:"username" validate:"required,min=5,max=20"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"ageValidator"`
}
在此示例中,我们有一个 User 结构,其中包含三个字段 Username、 Email、 和Age 。我们使用 Go Validator V10 语法添加了验证标签,以指定每个字段的验证规则。
现在,你可以将 validation 标签应用于 User 结构或任何其他数据结构中需要年龄验证的任何字段: age
通过创建自定义验证器,您可以根据特定应用程序的需求定制输入验证,从而确保数据处理的准确性和安全性。 它简化了以下任务:
添加特定于应用程序需求的自定义验证规则。
轻松转换错误消息以提供用户友好的反馈。
只需几行代码即可快速注册自定义验证器。
- 4 输入验证
验证输入:validator提供了内置验证标签外 现在,让我们实现 API 端点并验证传入的数据:
go
// 注册新用户
func createUserHandler(ctx *gin.Context) {
// createUserHandler(w http.ResponseWriter, r *http.Request) {
var user User
//r := ctx.Request
//w := ctx.Writer
// Parse the JSON request and populate the User struct
err := ctx.ShouldBindJSON(&user)
logs.Printf("receive user:%#v \n", user)
if err != nil {
// http.Error(w, "Invalid JSON", http.StatusBadRequest)
ctx.JSON(http.StatusBadRequest, "Invalid JSON")
return
}
// Create a new validator instance and RegisterValidation
validate := validator.New()
validate.RegisterValidation("ageValidator", ageValidator)
// Validate the User struct
err = validate.Struct(user)
if err != nil {
// Validation failed, handle the error
errors := err.(validator.ValidationErrors)
// http.Error(w, fmt.Sprintf("Validation error: %s", errors), http.StatusBadRequest)
ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Validation error: %s", errors))
return
}
// Validation passed, proceed to process the user data
// Your application logic goes here...
// Send a success response
// w.WriteHeader(http.StatusOK)
// fmt.Fprintf(w, "User created successfully!")
ctx.JSON(http.StatusOK, gin.H{"data": fmt.Sprintf("Validation success: %v", user), "code": 10000})
}
在此代码片段中,我们首先解析传入的 JSON 数据并填充 User 结构。 然后,我们创建一个新的验证程序实例,并使用 validate.Struct(user). 如果验证失败,将返回包含特定验证错误的错误消息。否则,我们的应用程序逻辑可以继续处理经过验证的数据。
3 校验和测试
绑定注册函数到路由
go
func WebValidationService(addr string) error {
oe := gin.Default()
busses := oe.Group("/user")
busses.POST("/new", createUserHandler) //
if addr == "" {
addr = "0.0.0.0:2131"
}
fmt.Printf("board Web service:%v\n", addr)
return oe.Run(addr)
}
启动服务后调用校验测试
less
curl --location --request POST "http://127.0.0.1:881/user/new" ^
--header "User-Agent: Apifox/1.0.0 (https://www.apifox.cn)" ^
--header "Content-Type: application/json" ^
--header "Accept: */*" ^
--header "Host: 127.0.0.1:881" ^
--header "Connection: keep-alive" ^
--data-raw "{ \"email\":\"2312@da.com\", \"age\":122}"
返回错误
css
"Validation error: Key: 'User.Age' Error:Field validation for 'Age' failed on the 'ageValidator' tag"
3 完整的验证标签支持列表
-
Field
scssTag Description eqcsfield Field Equals Another Field (relative) eqfield Field Equals Another Field fieldcontains Check the indicated characters are present in the Field fieldexcludes Check the indicated characters are not present in the field gtcsfield Field Greater Than Another Relative Field gtecsfield Field Greater Than or Equal To Another Relative Field gtefield Field Greater Than or Equal To Another Field gtfield Field Greater Than Another Field ltcsfield Less Than Another Relative Field ltecsfield Less Than or Equal To Another Relative Field ltefield Less Than or Equal To Another Field ltfield Less Than Another Field necsfield Field Does Not Equal Another Field (relative) nefield Field Does Not Equal Another Field
-
Network:
cssTag Description cidr Classless Inter-Domain Routing CIDR cidrv4 Classless Inter-Domain Routing CIDRv4 cidrv6 Classless Inter-Domain Routing CIDRv6 datauri Data URL fqdn Full Qualified Domain Name (FQDN) hostname Hostname RFC 952 hostname_port HostPort hostname_rfc1123 Hostname RFC 1123 ip Internet Protocol Address IP ip4_addr Internet Protocol Address IPv4 ip6_addr Internet Protocol Address IPv6 ip_addr Internet Protocol Address IP ipv4 Internet Protocol Address IPv4 ipv6 Internet Protocol Address IPv6 mac Media Access Control Address MAC tcp4_addr Transmission Control Protocol Address TCPv4 tcp6_addr Transmission Control Protocol Address TCPv6 tcp_addr Transmission Control Protocol Address TCP udp4_addr User Datagram Protocol Address UDPv4 udp6_addr User Datagram Protocol Address UDPv6 udp_addr User Datagram Protocol Address UDP unix_addr Unix domain socket end point Address uri URI String url URL String http_url HTTP URL String url_encoded URL Encoded urn_rfc2141 Urn RFC 2141 String
-
Strings:
sqlTag Description alpha Alpha Only alphanum Alphanumeric alphanumunicode Alphanumeric Unicode alphaunicode Alpha Unicode ascii ASCII boolean Boolean contains Contains containsany Contains Any containsrune Contains Rune endsnotwith Ends Not With endswith Ends With excludes Excludes excludesall Excludes All excludesrune Excludes Rune lowercase Lowercase multibyte Multi-Byte Characters number Number numeric Numeric printascii Printable ASCII startsnotwith Starts Not With startswith Starts With uppercase Uppercase
-
Format:
typescriptTag Description base64 Base64 String base64url Base64URL String base64rawurl Base64RawURL String bic Business Identifier Code (ISO 9362) bcp47_language_tag Language tag (BCP 47) btc_addr Bitcoin Address btc_addr_bech32 Bitcoin Bech32 Address (segwit) credit_card Credit Card Number mongodb MongoDB ObjectID mongodb_connection_string MongoDB Connection String cron Cron spicedb SpiceDb ObjectID/Permission/Type datetime Datetime e164 e164 formatted phone number email E-mail String eth_addr Ethereum Address hexadecimal Hexadecimal String hexcolor Hexcolor String hsl HSL String hsla HSLA String html HTML Tags html_encoded HTML Encoded isbn International Standard Book Number isbn10 International Standard Book Number 10 isbn13 International Standard Book Number 13 issn International Standard Serial Number iso3166_1_alpha2 Two-letter country code (ISO 3166-1 alpha-2) iso3166_1_alpha3 Three-letter country code (ISO 3166-1 alpha-3) iso3166_1_alpha_numeric Numeric country code (ISO 3166-1 numeric) iso3166_2 Country subdivision code (ISO 3166-2) iso4217 Currency code (ISO 4217) json JSON jwt JSON Web Token (JWT) latitude Latitude longitude Longitude luhn_checksum Luhn Algorithm Checksum (for strings and (u)int) postcode_iso3166_alpha2 Postcode postcode_iso3166_alpha2_field Postcode rgb RGB String rgba RGBA String ssn Social Security Number SSN timezone Timezone uuid Universally Unique Identifier UUID uuid3 Universally Unique Identifier UUID v3 uuid3_rfc4122 Universally Unique Identifier UUID v3 RFC4122 uuid4 Universally Unique Identifier UUID v4 uuid4_rfc4122 Universally Unique Identifier UUID v4 RFC4122 uuid5 Universally Unique Identifier UUID v5 uuid5_rfc4122 Universally Unique Identifier UUID v5 RFC4122 uuid_rfc4122 Universally Unique Identifier UUID RFC4122 md4 MD4 hash md5 MD5 hash sha256 SHA256 hash sha384 SHA384 hash sha512 SHA512 hash ripemd128 RIPEMD-128 hash ripemd128 RIPEMD-160 hash tiger128 TIGER128 hash tiger160 TIGER160 hash tiger192 TIGER192 hash semver Semantic Versioning 2.0.0 ulid Universally Unique Lexicographically Sortable Identifier ULID cve Common Vulnerabilities and Exposures Identifier (CVE id)
-
Comparisons:
sqlTag Description eq Equals eq_ignore_case Equals ignoring case gt Greater than gte Greater than or equal lt Less Than lte Less Than or Equal ne Not Equal ne_ignore_case Not Equal ignoring case
-
Other:
sqlTag Description dir Existing Directory dirpath Directory Path file Existing File filepath File Path image Image isdefault Is Default len Length max Maximum min Minimum oneof One Of required Required required_if Required If required_unless Required Unless required_with Required With required_with_all Required With All required_without Required Without required_without_all Required Without All excluded_if Excluded If excluded_unless Excluded Unless excluded_with Excluded With excluded_with_all Excluded With All excluded_without Excluded Without excluded_without_all Excluded Without All unique Unique
-
Aliases:
Tag Description iscolor hexcolor|rgb|rgba|hsl|hsla country_code iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric
4 附:完整代码
go
type User struct {
Email string `json:"email" binding:"required,email"`
Age int `json:"age" validate:"ageValidator"` // 年龄范围 0-120
}
// 解析用户结构体
func createUserHandler(ctx *gin.Context) {
// createUserHandler(w http.ResponseWriter, r *http.Request) {
var user User
//r := ctx.Request
//w := ctx.Writer
// Parse the JSON request and populate the User struct
err := ctx.ShouldBindJSON(&user)
logs.Printf("receive user:%#v \n", user)
if err != nil {
// http.Error(w, "Invalid JSON", http.StatusBadRequest)
ctx.JSON(http.StatusBadRequest, "Invalid JSON")
return
}
// Create a new validator instance
validate := validator.New()
validate.RegisterValidation("ageValidator", ageValidator)
// Validate the User struct
err = validate.Struct(user)
if err != nil {
// Validation failed, handle the error
errors := err.(validator.ValidationErrors)
// http.Error(w, fmt.Sprintf("Validation error: %s", errors), http.StatusBadRequest)
ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Validation error: %s", errors))
return
}
// Validation passed, proceed to process the user data
// Your application logic goes here...
// Send a success response
// w.WriteHeader(http.StatusOK)
// fmt.Fprintf(w, "User created successfully!")
ctx.JSON(http.StatusOK, gin.H{"data": fmt.Sprintf("Validation success: %v", user), "code": 10000})
}
//校验函数
func ageValidator(fl validator.FieldLevel) bool {
maxPercent := decimal.NewFromInt(1)
minPercent := decimal.NewFromInt(0)
quantity := decimal.NewFromInt(100)
switch v := fl.Field(); v.Kind() {
case reflect.String:
val, err := decimal.NewFromString(v.String())
logs.Println("val before /100 :", val)
val = val.Div(quantity) // / 100.0
logs.Println("val after /100 :", val)
if err == nil && val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
return true
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val := decimal.NewFromInt(v.Int())
logs.Println("val before /100 :", val)
val = val.Div(quantity)
logs.Println("val after /100 :", val)
if val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
return true
}
case reflect.Float32, reflect.Float64:
val := decimal.NewFromFloat(v.Float())
logs.Println("val before /100 :", val)
val = val.Div(quantity)
logs.Println("val after /100 :", val)
if val.Abs().GreaterThanOrEqual(minPercent) && val.Abs().LessThanOrEqual(maxPercent) {
return true
}
default:
return false
}
return false
}
//服务路由
func WebValidationService(addr string) error {
oe := gin.Default()
busses := oe.Group("/user")
busses.POST("/new", createUserHandler) //
if addr == "" {
addr = "0.0.0.0:2131"
}
fmt.Printf("board Web service:%v\n", addr)
return oe.Run(addr)
}
5 小结
Validator V10 提供的内置验证标签外,您还可以灵活地创建针对您的特定应用程序要求定制的自定义验证器。自定义验证器允许您对数据执行特定于域的规则,从而确保更精确的输入验证。
可以轻松地在 Go 应用程序中实现输入参数验证。验证输入参数有助于防止潜在的安全漏洞,并确保我们的应用程序使用的数据准确且一致。该库丰富的验证规则集使其成为使用 Go 的开发人员的强大工具。
请始终验证应用程序中的输入数据,以保证其可靠性和安全性.尽管输入验证很有用,但它不是一种最安全的措施,必要时使用安全插件仍然很重要。