Go html/template 怎么安全把后端数据交给前端:别把 JSON 硬塞进 template.JS
做 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 文档 与 包说明源码。
Go 服务怎么用 Sec-Fetch-Site 拦住跨站请求:Fetch Metadata 的最小中间件
- 上一篇
- Go 服务怎么用 Sec-Fetch-Site 拦住跨站请求:Fetch Metadata 的最小中间件
- 下一篇
- Java JTable 双击怎么拿到正确行:MouseAdapter、排序转换和空白行判断
-
- Golang · Go教程 | 7小时前 | 内存 · JSON · 性能优化 · Go教程 · json.Decoder · Go 流式解析 json.Decoder 超大JSON 峰值内存 批次写入
- Go 处理超大 JSON 怎么降峰值内存:json.Decoder 流式读取、批次落库与压测对比
- 310浏览 收藏
-
- Golang · Go教程 | 7小时前 | golang · HTTP · go · 服务端 · 实战 · 安全配置 · http.server MaxBytesReader ReadHeaderTimeout Go HTTP 服务 请求体限制 响应头限制
- Go HTTP 服务怎么设请求边界:请求体、超时和响应头的生产配置
- 455浏览 收藏
-
- Golang · Go教程 | 2天前 | golang · 缓存 · singleflight · go · 高并发 · 后端开发 · Go 并发控制 缓存失效 请求合并 singleflight 回源
- Go singleflight 怎么合并同一请求:缓存失效时别让 500 个请求一起回源
- 109浏览 收藏
-
- Golang · Go教程 | 2天前 | [] · []
- Go 项目 GitHub Actions 怎么设质量门禁:go vet、go test 与构建分阶段拦截
- 485浏览 收藏
-
- Golang · Go教程 | 2天前 | 前端开发 · Go教程 · html/template · 网页模板 · 导航 · Go html/template CurrentPath 导航高亮 template.FuncMap Go网页模板
- Go html/template 怎么高亮当前导航:传入 CurrentPath 的最小写法
- 409浏览 收藏
-
- Golang · Go教程 | 3天前 | 标准库 · 基准测试 · Go教程 · 字符串拼接 · Go 字符串拼接 strings.Builder bytes.Buffer []byte
- Go 拼接字符串怎么选:strings.Builder、bytes.Buffer 和 []byte 的边界
- 188浏览 收藏
-
- Golang · Go教程 | 3天前 | golang · Timer · 并发编程 · time.After · 性能排查 · time.After go timer Go 1.23 NewTimer Timer.Reset Timer.Stop
- Go 1.23 以后还要手动 Stop Timer 吗:一次超时循环改造实战
- 403浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 4574次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4225次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4183次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4402次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4365次使用
-
- 国家医保服务平台亲情账户怎么绑定:给老人孩子用医保码要注意什么
- 2026-07-08 480浏览
-
- Go 项目 GitHub Actions 怎么设质量门禁:go vet、go test 与构建分阶段拦截
- 2026-07-17 485浏览
-
- Go 1.22 后 for range 闭包变量还要手动复制吗:循环变量语义变化和迁移检查
- 2026-07-08 348浏览
-
- Go 解析 JSON 用 struct 还是 map[string]any:RawMessage 和严格校验怎么选
- 2026-07-08 250浏览
-
- Go time.After 写在循环里会泄漏吗:定时器堆积、Stop 和 NewTimer 复用怎么判断
- 2026-07-08 181浏览

