应使用白名单过滤(如league/html-sanitizer)保留必要标签与属性,模型中通过mutator自动处理,Blade中用自定义组件或客户端二次过滤,并禁止base64图片直存数据库。

富文本内容不能直接存进 Laravel 的 text 字段就完事——得防 XSS、要过滤标签、还得让前端安全渲染,否则编辑器一粘贴 JS 就执行,用户数据一导出就乱码。
怎么存富文本才不被 XSS 攻击
很多人用 htmlspecialchars() 或直接 strip_tags() 一刀切,结果把 <img>、<ul> 全干掉了,编辑器白集成。真正该做的是「白名单过滤」:只保留业务需要的标签和属性。
- 推荐用
league/html-sanitizer,比原生strip_tags()可控得多,能配置允许src(但只限https?协议)、禁用onerror这类事件属性 - 别在 Controller 里手写正则过滤 —— 正则很难覆盖
<img src="x" onerror=alert(1)>这种嵌套绕过 - 存储前必须过滤,不能只靠前端或 Blade 的
{{ $content }}—— 用户绕过前端直调 API 时照样中招
Laravel 模型里怎么自动处理富文本字段
手动在每个 save() 前调 sanitize() 太容易漏,用 Eloquent 的 set* mutator 最省心,而且天然兼容批量赋值和 API Resource。
- 在模型里加
setContentAttribute($value)方法,里面调用 sanitizer 实例 - 注意:mutator 不会触发在
update(['content' => $raw])这种数组更新里,得配合fill()或显式调用$model->content = $raw - 如果字段名是
description,就得写setDescriptionAttribute(),函数名必须严格匹配字段驼峰规则
public function setContentAttribute($value)
{
$this->attributes['content'] = app('html_sanitizer')->sanitize($value);
}
Blade 里怎么安全输出又不失样式
用 {!! $post->content !!} 是最常见也最危险的操作——它等于把过滤后的 HTML 直接插入 DOM,一旦 sanitizer 漏了什么,就全暴露了。
- 优先用
x-content自定义组件封装输出逻辑,内部统一加SanitizeHtmldirective 或DOMPurify.sanitize()客户端二次过滤 - 如果必须用
{!! !!},确保该字段已通过服务端白名单过滤,且模板里没混用{{ }}和{!! !!}渲染同一字段 - 富文本里带内联样式(比如
style="color:red")时,league/html-sanitizer默认不放行,得显式配置AllowedAttributes::all()->with('style')
编辑器上传图片后路径怎么存才不翻车
很多编辑器(如 Quill、Tiptap)默认把图片转成 base64 存数据库,几百 KB 一张图,查一次列表就拖垮 MySQL。真要存,也得走独立附件表 + 云存储。
- 禁止在
content字段里存data:image/png;base64,...—— 它让字段膨胀、备份变慢、搜索失效 - 上传接口返回真实 URL(如
https://cdn.example.com/uploads/abc123.jpg),编辑器插入这个地址;后端只校验域名是否在白名单(比如只允许cdn.example.com) - 如果要用本地存储,图片必须存在
public/uploads下,并用Storage::disk('public')管理,避免路径硬编码或权限错误
真正麻烦的不是集成编辑器,而是每次需求变更时——比如运营突然说“要支持视频”,你得立刻检查 sanitizer 是否放行 <video>、是否限制 src 协议、是否要加 poster 图片校验。这些边界条件,文档里从不写,只能靠测试用例卡住。


















