Go html/template 怎么安全把后端数据交给前端:别把 JSON 硬塞进 template.JS
来源:17golang原创
时间:2026-07-17 12:50:07 177浏览 收藏
做 Go 服务端渲染时,首屏经常要把用户信息、功能开关和分页条件交给一小段前端脚本。数据里一旦出现引号、换行或尖括号,很多人会先 json.Marshal,再把结果转成 template.JS 塞进页面。页面看起来立刻正常了,但这一步也绕开了 html/template 原本替你做的上下文转义。
要点速览
- 把 Go 结构体直接放在普通
的 JavaScript 值位置,html/template会按上下文生成可用的 JSON。 - 不要把来自请求、数据库或第三方接口的 JSON 仅靠转为
template.JS标记成安全内容。 - 页面只需要一个短字段时,
data-user-id这类属性比塞一整段初始化对象更直观。 - 回归时要带上引号、
片段和 Unicode 字符,确认页面没有长出额外标签或截断脚本。
问题不在 JSON,而在它被放进了哪个上下文
先看一个常见场景:服务端想让浏览器拿到当前用户和实验开关,于是在模板里写 const pageData = {{ .Page }};。这不是简单的字符串替换。html/template 会识别当前动作位于 JavaScript 上下文;传入的是结构体或 map 时,它会生成适合该位置的 JSON。这个能力的前提是模板由可信开发者编写,而渲染数据可以是不可信的。
真正危险的捷径是把一段外部 JSON 直接包装成 template.JS。这个类型表示“已知安全的 JavaScript 表达式”,责任随之落到调用方。官方文档明确提醒:即便 JSON 在语法上有效,只要数据不可信,就不该因为它像 JSON 而跳过模板的处理。
最小配方:让结构体进入 script 的值位置
先不要手工拼引号,也不用预先把对象编码成字符串。给视图模型一个清晰的边界,再把它直接交给模板。下面是模板片段与数据类型;项目里的渲染入口照常把 Page 作为数据的一部分传入即可。
package view
import "html/template"
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
type PageData struct {
User User `json:"user"`
Flags []string `json:"flags"`
}
type PageModel struct {
Page PageData
}
const pageHTML = `
`
func parsePage() (*template.Template, error) {
return template.New("profile").Parse(pageHTML)
}
关键是 {{ .Page }} 前后没有再包一层手写引号。PageData 不是普通文本,它在脚本值位置被模板按 JavaScript 规则处理。先在本地用用户名 Li "A"、 和包含换行的备注走一遍页面:浏览器应该拿到同一个数据值,但 DOM 中不能多出一段意外的标签或脚本。

前端读取时,把初始化对象当数据而不是模板碎片
有了 pageData,前端代码就按普通对象读取。这个阶段最容易被忽略的细节是:把数据用于 textContent、属性赋值或受控的组件状态,而不是拿字符串再拼一段 HTML。这样前后端的责任也更清楚——Go 负责输出结构化初始数据,浏览器代码负责按 DOM API 渲染。
const badge = document.querySelector("#profile");
if (pageData.flags.includes("beta")) {
badge.dataset.mode = "beta";
}
badge.textContent = `${pageData.user.name} #${pageData.user.id}`;
这一步可以顺手做一个核对:打开浏览器开发者工具,确认 pageData 是对象而不是一串双重编码的 JSON 字符串;再确认页面显示的是原始文字,不是 " 一类转义残留。若拿到的是字符串,通常是某层又做了一次 json.Marshal,而不是模板失效。
只传一个短字段时,data-* 往往更省心
并非每个页面都需要一个初始化对象。比如一个“编辑资料”按钮只要当前用户 ID,直接写到 data-user-id 更容易审查,也不必让页面多一段脚本数据。属性值仍由 html/template 处理,前端用 dataset 读取即可。
这里的边界很实际:少量标量放属性没问题;筛选器、权限集合、嵌套对象或上百条列表,不适合拆成一堆 data-*。数据开始变复杂时,回到上面的结构体配方,或者通过一个受认证的 JSON 接口按需加载。

template.JS 不是给 JSON 开的绿色通道
template.JS 仍有合理用途,例如模板作者自己写死的一小段 JavaScript 表达式,并且能证明内容不含外部输入。它不是“JSON 字符串专用类型”,更不是给数据库字段、查询参数、CMS 内容或上游接口返回值消除转义的工具。
如果上游给的是 JSON 文本,先用 json.Unmarshal 解析成 Go 结构体或 map,再把该对象传给模板。解析失败就把它当作数据错误处理,而不是为了让页面先出来而原样塞进脚本。这个取舍看似多一步,却让类型、错误和安全边界都留在服务端。
上线前用四个输入检查渲染结果
模板安全问题往往不会在普通中文用户名上暴露。给页面准备四组固定样本,检查响应源码和浏览器显示,比只看“页面能打开”可靠得多:
- 普通值:
Lin,确认对象字段和页面文字都正常。 - 引号值:
Lin "A",确认脚本没有提前结束,JavaScript 仍能读取对象。 - 标签样本:包含
或的文本,确认浏览器没有新增可运行标签。 - Unicode 与换行:确认中文、emoji 和多行备注不会变成双重编码字符串。
同时别忘了确认服务使用的是 html/template 而不是 text/template。两者 API 很像,但只有前者会针对 HTML、JavaScript、CSS 和 URL 上下文处理输出。升级 Go 版本时,也应把这组样本放进回归测试,尤其是项目有大量 script 模板片段时。
相关问题
能把 JSON 放进 type="application/json" 的 script 标签吗?
可以作为另一种页面数据载体,但要先验证所用 Go 版本与模板写法的上下文处理结果。若只是把 Go 对象交给页面的 JavaScript,普通 script 的值位置配合 {{ .Page }} 更直接,也更容易按官方文档理解。
data-* 能放一个完整 JSON 对象吗?
不建议作为默认方案。属性适合短字段;对象越大,转义、长度和调试成本越高。复杂数据用结构体进入 script 值位置,或通过 API 按需请求更清晰。
template.HTML 和 template.JS 的风险一样吗?
两者都是“已知安全内容”的显式标记,都会把审查责任交给调用方。只有内容确实由可信模板作者固定写死,且能证明不会混入外部数据时,才考虑使用。
为什么页面里的对象字段名不是我想要的小写?
为 Go 的结构体字段补上 json 标签,例如 json:"user"。这样模板在 JavaScript 上下文序列化时能得到稳定、便于前端读取的字段名。
把安全边界留在模板上下文里
服务端渲染页面并不排斥前端交互,关键是别把后端数据降成一段手工拼接的脚本文本。对象就按对象传给 html/template,短字段就用 data-*;只有真正可信的表达式才考虑 typed string。这样处理后,用户输入再复杂,模板上下文仍是那条最可靠的边界。
关于 JavaScript 上下文、typed string 和安全模型,可继续阅读 Go 官方的 html/template 文档 与 包说明源码。
-
480 收藏
-
485 收藏
-
348 收藏
-
250 收藏
-
181 收藏
-
Golang · Go教程 | 9小时前 | golang · 缓存 · singleflight · go · 高并发 · 后端开发 · Go 并发控制 缓存失效 请求合并 singleflight 回源109 收藏
-
485 收藏
-
Golang · Go教程 | 15小时前 | 前端开发 · Go教程 · html/template · 网页模板 · 导航 · Go html/template CurrentPath 导航高亮 template.FuncMap Go网页模板409 收藏
-
188 收藏
-
Golang · Go教程 | 1天前 | golang · 配置 · 设计模式 · go · client · api设计 · Go 构造函数 Functional Options 函数选项 Client 配置 API 兼容454 收藏
-
Golang · Go教程 | 1天前 | golang · Timer · 并发编程 · time.After · 性能排查 · time.After go timer Go 1.23 NewTimer Timer.Reset Timer.Stop403 收藏
-
Golang · Go教程 | 1天前 | 并发 · 错误处理 · go · Context · 排错 · Go 错误链 context.WithCancelCause context.Cause 取消原因366 收藏
-
Golang · Go教程 | 1天前 | 并发 · WaitGroup · go · worker pool · 服务停机 · Go channel WaitGroup 优雅停机 worker pool 任务排空207 收藏
-
294 收藏
-
Golang · Go教程 | 1天前 | 切片 · Slices · Go教程 · Go 1.21 · 切片删除 Go教程 Go slices.Delete slices.DeleteFunc Go 1.21463 收藏
-
Golang · Go教程 | 1天前 | HTTP · Go教程 · 服务治理 · Shutdown · SIGTERM · SIGTERM Go 优雅关闭 http.Server Shutdown Go HTTP 服务 连接收尾 服务下线316 收藏
-
Golang · Go教程 | 1天前 | 错误处理 · Go教程 · errors.Join · errors.Is · Go 1.20 · 错误包装 errors.Is errors.As Go errors.Join 多错误处理 Go 1.20431 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习