在服务中的输入和输出的验证
1 简介
创建强大且安全的 API(应用程序编程接口)至关重要。无论您是构建 RESTful 服务、微服务还是成熟的 Web 应用程序,数据验证都是确保 API 完整性和可靠性的关键方面。
本文将展示在web服务中如何完成请求有效负载验证,包括验证输入/过滤输入,使用反射和 validator 模块编写自定义验证,以及为各种格式(如 TOML 等)构建自定义绑定,通过验证输入以达到向用户输出有意义的错误消息。
同时通过严格限制和验证输入数据的格式,也可以防止恶意字符进入SQL查询。通常结合正则表达式或白名单策略实现。
2 实现方式
在Gin中,可以使用正则表达式或 strconv 等工具对输入进行验证。
示例:输入验证
r.GET("/user", func(c *gin.Context) {
id := c.Query("id")
// 验证id为数字
if _, err := strconv.Atoi(id); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
return
}
var username string
err := db.QueryRow("SELECT username FROM users WHERE id = ?", id).Scan(&username)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, gin.H{"username": username})
})
在 Gin 服务中,可以通过参数校验工具和严格规范的 SQL 构造方式,确保传递变量时安全高效。以下是一些具体实现方式和相关工具的介绍。
3 参数校验的实现方式
-
- 使用 gin 的 binding 标签进行参数校验
框架Gin 提供了内置的参数绑定和校验功能,可以通过 binding 标签对结构体字段进行验证。
在内部使用 validator 包进行验证。此包验证程序提供了一组广泛的内置验证,包括 required必填验证、 类型验证和字符串验证。验证程序包还支持更复杂的验证,例如len ,min和 max。
验证通过 struct 标签添加到结构体中:binding
示例:校验请求参数
type UserQuery struct {
ID int `form:"id" binding:"required,numeric"` // 必须且是数字
Name string `form:"name" binding:"omitempty,alphanum"` // 可选且是字母数字
}
func main() {
r := gin.Default()
r.GET("/user", func(c *gin.Context) {
var query UserQuery
// 参数绑定和校验
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 模拟数据库查询
c.JSON(http.StatusOK, gin.H{
"id": query.ID,
"name": query.Name,
})
})
r.Run()
}
校验规则通过 binding 标签指定,常用校验规则包括:
required: 必填字段。
numeric: 必须是数字。
alphanum: 必须是字母数字组合。
email: 必须是有效邮箱格式。
min/max: 设置数值或字符串长度限制。
其他标记 描述 使用示例
uppercase 只接受大写字母 binding:"uppercase"
lowercase 只接受小写字母 binding:"lowercase"
contains 仅接受包含特定字符串段的字符串。 binding:"contains=key"
alphanum 仅接受字母数字字符(英文字母和数字)。拒绝包含特殊字符的字符串。 binding:"alphanum"
alpha 只接受英文字母 binding:"alpha"
endswith 仅接受以特定字符序列结尾的字符串 binding:"endswith=."
假设您需要验证以字符串 prefix 开头的 10 个字母的产品代码。对于此方案,您可以将标签PC与startswith标签一起使用:
type Body struct {
ProductCode string `json:"productCode" binding:"required,startswith=PC,len=10"`
}
要将请求正文绑定到类型中,请使用 model binding。目前支持 JSON、XML 和标准表单值 (foo=bar&boo=baz) 的绑定。
Gin 绑定是一个很棒的反序列化库。它支持 JSON、XML、查询参数等开箱即用的功能,并附带一个内置的验证框架。
可以用于将 JSON、XML、路径参数、表单数据等序列化为结构和映射。它还具有一个内置验证框架,其中包含复杂的验证。
通过提供 struct 标签来支持各种格式。例如,tag 用于序列化 path 参数,并且 使用 go-playground/validator.v8 进行验证。在此处查看有关标签使用的完整文档。
注意,你需要在需要绑定的所有字段上设置相应的 binding 标签。例如,从 JSON 绑定时,将 .json:“fieldname”
此外,Gin 还提供了两组绑定方法:
- 必须绑定
方法函数 - Bind, BindJSON, BindQuery
行为 - 这些方法在后台使用。如果存在绑定错误,则请求将中止,并显示错误 。
这会将响应状态代码设置为 400,并将标头设置为Content-Typetext/plain 。
请注意,如果您尝试在此之后设置响应代码,将导致 警告 WARNING 。
如果您希望更好地控制行为,请考虑使用等效方法。
MustBindWithc.AbortWithError(400, err).SetType(ErrorTypeBind)Content-Typetext/plain; charset=utf-8[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422ShouldBind
- 应当绑定
方法 - ShouldBind、 ShouldBindJSON、ShouldBindQuery
行为 - 这些方法在后台使用。如果存在绑定错误,则会返回错误,开发人员有责任适当地处理请求和错误。
ShouldBindWith使用 Bind 方法时,Gin 会尝试根据 Content-Type 标头推断 Binder。
如果您确定要绑定的内容,则可以使用 MustBindWith或 ShouldBindWith。
您还可以指定特定字段为必填字段。如果字段在绑定时被修饰为空值,则会返回错误。binding:“required”
// Binding from JSON
type Login struct {
User string `form:"user" json:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
func main() {
router := gin.Default()
// Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) {
var json Login
if err := c.ShouldBindJSON(&json); err == nil {
if json.User == "manu" && json.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
// Example for binding a HTML form (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) {
var form Login
// This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&form); err == nil {
if form.User == "manu" && form.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
// Listen and serve on 0.0.0.0:8080
router.Run(":8080")
}
调用示例请求
$ curl -v -X POST \
http://localhost:8080/loginJSON \
-H 'content-type: application/json' \
-d '{ "user": "manu" }'
> POST /loginJSON HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> content-type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Date: Fri, 04 Aug 2017 03:51:31 GMT
< Content-Length: 100
<
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
4 小结
并非所有用例都非常适合内置 Gin 验证。因此,Gin 提供了添加自定义验证的方法。
在验证过程中使用reflect 该反射包在运行时确定 struct 字段的类型和值。
通过API 验证可以提供如下优异体验:
API 充当不同软件组件之间的通信桥梁,支持数据交换。确保发送到 API 终端节点的数据有效且符合预期格式至关重要:
保障数据完整性:
验证传入数据有助于维护应用程序数据的完整性。无效或恶意制作的输入可能会导致数据损坏或不一致,从而可能导致应用程序错误或安全漏洞。
提供一定安全:
适当的验证可以防止常见的安全漏洞,例如 SQL 注入、跨站点脚本 (XSS) 和跨站点请求伪造 (CSRF)。通过拒绝格式错误或恶意的输入,您可以强化 API 以抵御各种攻击。
提升用户体验:
在 API 级别验证数据可确保客户端在提交不正确的数据时收到有意义的错误消息。这通过提供有关问题所在以及如何纠正错误的明确反馈来增强用户体验。
- 点赞
- 收藏
- 关注作者
评论(0)