
本文讲解在数据库中安全存储用户输入的含 <a> 和 <br> 标签的富文本(如描述字段),重点解决 URL 中 & 符号被双重转义导致解析失败的问题,并提供编码/解码与字符串替换两种可靠方案。
本文讲解在数据库中安全存储用户输入的含 `` 和 `
` 标签的富文本(如描述字段),重点解决 url 中 `&` 符号被双重转义导致解析失败的问题,并提供编码/解码与字符串替换两种可靠方案。
在 Web 应用中,常需将用户通过 <textarea> 输入的带格式文本(例如含换行转 <br>、链接转 <a href="https://www.php.cn/link/263b1243ca2dbeb358777ceabc4a2e4c">)持久化至数据库。但若直接对 HTML 字符串做 Base64 编码(如 btoa())再存储,极易因特殊字符(尤其是 URL 中的 &)引发解码失败——典型报错:Uncaught DOMException: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded. 这是因为 & 在 HTML 中常被自动转义为 &,而 btoa() 无法处理已转义的 Unicode 字符串(如 & 对应字节序列不合法)。
✅ 推荐方案:服务端预处理 + 客户端安全还原
最稳定、可维护性最强的方式是避免使用 btoa/atob 处理含 HTML 的文本,改用语义清晰的字符串替换策略:
1. 存储前:将 & 替换为占位符(或直接保留原始 &)
// 假设用户输入的 HTML 片段如下: const rawHtml = 'Visit <a href="https://example.com/?q=1&p=2">Example</a><br>Line break'; // ✅ 正确做法:统一还原为原始 &(前提是前端生成 HTML 时未过度转义) const safeForDb = rawHtml.replace(/&/g, '&'); // 或更保守地使用唯一占位符(防冲突): // const safeForDb = rawHtml.replace(/&/g, '__AMP__');
2. 读取后:按需还原并渲染(确保 XSS 安全!)
// 从 DB 读取后,恢复 &(若用了占位符则反向替换)
const fromDb = 'Visit <a href="https://example.com/?q=1&p=2">Example</a><br>Line break';
// const fromDb = rawHtml.replace(/__AMP__/g, '&');
// ⚠️ 关键:绝不直接 innerHTML!先做白名单过滤
function sanitizeHtml(html) {
const div = document.createElement('div');
div.innerHTML = html;
// 仅保留 <a> 和 <br>,移除其他标签及危险属性
const allowedTags = ['A', 'BR'];
const nodes = Array.from(div.childNodes);
nodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (!allowedTags.includes(node.tagName)) {
node.remove();
} else if (node.tagName === 'A') {
const href = node.getAttribute('href');
// 仅允许 http(s):// 开头的 href,且无 javascript: 等危险协议
if (!href || !/^https?:\/\//i.test(href)) {
node.removeAttribute('href');
}
}
}
});
return div.innerHTML;
}
// 渲染到页面
element.innerHTML = sanitizeHtml(fromDb); // 安全输出❌ 为什么不推荐 btoa/atob?
- btoa() 仅支持 Latin-1 字节(0–255),而 & 是 UTF-8 编码的多字节序列,直接传入会抛出 InvalidCharacterError;
- 即使前端强制 encodeURIComponent 后再 btoa,服务端也需严格 atob → decodeURIComponent 双重解码,链路复杂且易出错;
- 数据库中存储 Base64 字符串增大体积,且丧失可读性与索引能力。
✅ 最佳实践总结
- 存储层:使用 TEXT 类型字段(非 VARCHAR 超长限制),内容保持语义化 HTML(& 不转义);
- 输入层:前端生成链接时,确保 & 未被重复转义(检查富文本编辑器配置);
- 输出层:服务端或客户端必须执行白名单 HTML 过滤,禁用 on* 事件、javascript: 协议等;
- 扩展性:未来若需支持更多标签(如 <strong>),只需更新白名单与校验逻辑,无需重构存储格式。
遵循以上方案,即可在保障安全性与兼容性的前提下,稳健实现“带链接的富文本入库与渲染”。











