
本文介绍在 go web 应用中对 json 请求体中的字符串字段(如 title、content 等)进行 html 安全清洗与结构化校验的最佳实践,重点使用 bluemonday 实现 xss 防护,并结合 validator 进行语义级数据验证。
本文介绍在 go web 应用中对 json 请求体中的字符串字段(如 title、content 等)进行 html 安全清洗与结构化校验的最佳实践,重点使用 bluemonday 实现 xss 防护,并结合 validator 进行语义级数据验证。
在 Go 开发中,仅依赖 json.Unmarshal 或框架的 ReadJSON 方法反序列化请求数据是不安全的——它不会自动过滤恶意 HTML/JavaScript 内容(如 <script>alert(1)</script>),极易导致存储型或反射型 XSS 漏洞。正确的做法是在数据进入业务逻辑前,分两步处理:清洗(Sanitization) + 校验(Validation)。
✅ 推荐方案:bluemonday + validator.v2 组合使用
-
bluemonday是 Go 生态中最成熟、最严格的 HTML 清洗库,基于策略(Policy)模型,默认拒绝一切未显式允许的标签与属性,可精准控制哪些 HTML 元素可保留(如<p></p>、<a href></a>),彻底剥离危险内容。 -
validator.v2(或更现代的go-playground/validator/v10)用于字段级语义校验(如非空、长度、范围),确保数据符合业务规则。
以下是以你提供的 Post 结构体为例的完整防护流程:
1. 安装依赖
go get github.com/microcosm-cc/bluemonday go get gopkg.in/validator.v2
2. 定义带校验标签的结构体(推荐增强版)
import "gopkg.in/validator.v2"
type Post struct {
Id int `json:"Id"`
CreatedAt time.Time `json:"CreatedAt"`
UpdatedAt time.Time `json:"UpdatedAt"`
CreatorId int `json:"CreatorId"`
Editors []int `json:"Editors"`
Status Status `json:"Status"`
Title string `json:"Title" validate:"nonzero,max=200"` // 非空且 ≤200 字符
ShortDescription string `json:"ShortDescription" validate:"max=500"`
Description string `json:"Description"`
Content string `json:"Content" validate:"max=100000"` // 大文本仍需限制
Url string `json:"Url" validate:"nonzero,alphanum,ascii,min=3,max=128"`
Keywords string `json:"Keywords" validate:"max=500"`
Data []string `json:"Data" validate:"max=10"` // 限制数组长度
// 其他嵌套字段(User、Media、Category 等)建议单独清洗/校验,避免递归污染
}
3. 在 HTTP Handler 中执行清洗与校验
func createPostHandler(c *gin.Context) {
var post Post
if err := c.ShouldBindJSON(&post); err != nil { // 使用 ShouldBindJSON 替代 ReadJSON(更健壮)
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON"})
return
}
// ? Step 1: HTML 清洗(仅对可能含 HTML 的字符串字段)
policy := bluemonday.UGCPolicy() // 专为用户生成内容设计:允许基础格式化(<p>, <strong>, <a> 等),禁用 script/style/script-src
post.Title = policy.Sanitize(post.Title)
post.ShortDescription = policy.Sanitize(post.ShortDescription)
post.Content = policy.Sanitize(post.Content)
post.Keywords = policy.Sanitize(post.Keywords) // 关键词通常纯文本,但清洗无害且防误植
// ✅ Step 2: 结构化校验
if err := validator.Validate(&post); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "validation failed", "details": err.Error()})
return
}
// ? 安全入库
if err := db.Create(&post).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "save failed"})
return
}
c.JSON(http.StatusCreated, post)
}</a></strong></p>
⚠️ 关键注意事项
-
清洗时机:必须在
Unmarshal后、Validate前执行。若先校验再清洗,可能导致校验通过但实际存入被篡改的内容(如Title被截断后仍满足max=200)。 -
字段选择性清洗:仅对明确允许富文本的字段(如
Content,ShortDescription)调用Sanitize();纯标识类字段(如Url,Keywords)应优先用正则或专用校验器(如urltag),而非 HTML 清洗。 -
策略定制:
bluemonday.UGCPolicy()是通用起点,生产环境建议根据需求定制:policy := bluemonday.NewPolicy() policy.AllowStandardHeaders() policy.AllowLists() policy.AllowElements("p", "br", "strong", "em", "a") policy.AllowAttrs("href").OnElements("a") policy.RequireNoFollowOnLinks(true) // 自动添加 rel="nofollow" -
嵌套结构处理:
Post.Creator、Post.Media等指针或结构体字段需单独定义清洗逻辑,避免nilpanic;建议将清洗逻辑封装为方法(如post.Clean())。 -
替代方案提醒:避免使用正则手动移除
<script></script>—— XSS 绕过手法极多(大小写混淆、注释注入、事件属性等),正则无法覆盖全部场景。
通过以上组合策略,你能在保持开发者体验的同时,为用户输入构建一道可靠的 XSS 防御屏障,真正实现「输入即安全」。










