Laravel 学院文本编辑器及 XSS 攻击防范 —— 富文本编辑器 summernote 篇


接下来的几篇教程,学院君想就学院重构过程中用到的一些比较好的工具和扩展包做一些推介。先从编辑器和文本处理开始。

在本次学院重构过程中,一个重要的需求就是加入用户系统,并且让他们参与学院内容创作中来(UGC),那么这就需要为用户提供好的内容编辑器,这里由于老版学院基于 WordPress 构建,之前的内容都是通过富文本编辑器编辑的,那么很自然的,新学院为了兼容之前内容的编辑,就需要提供富文本编辑器,在这里,需要给 WordPress 自带的编辑器点个赞,毕竟是这么多年的老牌博客系统,在我用过的这么多的富文本编辑器中,WordPress 的是最好的,好在恰到好处,无论是外观还是提供的功能,下面是 WordPress 编辑器的界面:

wordpress编辑器

非常好用,这个编辑器是基于 TinyMCE 的改良版本,整合到学院来的难度比较大,所以学院君将目光转移到选择其他拿来即用的富文本编辑器,也就是目前学院在用的 summernote,一样的简洁漂亮,唯一欠缺的是图片库管理功能,不过现在没必要把问题复杂化,引入图片库意味着很多相关的前端工作,而作为一般用户来说,很少会在不同文章中频繁使用相同的图片,即使用的话也可以通过插入图片链接的方式来实现,所以你现在看到的学院编辑器是这样的:

Laravel学院编辑器

summernote 的体验还是不错的,引入也非常简单,下面是学院主题创建/编辑界面的代码片段:

<div class="form-group">
    <span class="required">*</span>
    <label for="summernote">正文</label>
    <div class="summernote-textarea">
        <textarea id="summernote" name="content">
           {{ htmlspecialchars($article->content) }}
        </textarea>
    </div>
</div>

要让 summernote 正常渲染,还需要引入以下 CSS 文件:

<link href="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.9/summernote.css" rel="stylesheet">

以及如下 JavaScript 文件:

<script src="http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.9/summernote.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/9.22.0/js/vendor/jquery.ui.widget.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/9.22.0/js/jquery.iframe-transport.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-file-upload/9.22.0/js/jquery.fileupload.min.js"></script>

下面三个 JavaScript 文件会在 summernote 中上传图片时用到,下面是渲染编辑器和上传图片所需的 Javascript 代码:

jQuery('#summernote').summernote({
        height: 450,
        styleTags: ['p', 'blockquote', 'pre', 'h3', 'h4', 'h5'],
        callbacks: {
            onImageUpload: function(files) {
                var data = new FormData();
                data.append("image", files[0]);
                jQuery.ajax({
                    data: data,
                    type: 'POST',
                    url: '{{ route('image.upload') }}',
                    cache: false,
                    contentType: false,
                    processData: false,
                    statusCode: {
                        200: function (data) {
                            if (data.success) {
                                jQuery('#summernote').summernote('insertImage', data.image.path, data.image.desc);
                            } else {
                                alert(data.message);
                            }
                        },
                        422: function (data) {
                            var result = jQuery.parseJSON(data.responseText);
                            jQuery.each(result.errors, function (field, errors) {
                                jQuery.each(errors, function (index, message) {
                                    alert(message);
                                })
                            });
                        },
                        404: function () {
                            alert('操作对象不存在');
                        },
                        403: function () {
                            alert('你没有权限进行上传操作');
                        },
                        413: function () {
                            alert('上传图片大小超出服务器承载极限');
                        },
                        401: function () {
                            alert('你没有权限进行上传操作,请先登录');
                        },
                        500: function () {
                            alert('服务器开小差了,请在线联系学院君');
                        }
                    }
                });
            }
        }
    });

图片上传实现代码位于 callbacks 中的 onImageUpload 方法。后端图片处理细节这里就不展开了,感兴趣的同学当个课后作业去实现下吧。编辑好内容后,通过下面的代码获取编辑器中的内容:

var content = jQuery('#summernote').summernote('code');

这样提交的内容有个安全隐患,那就是某些调皮的用户有可能在提交内容中注入恶意代码进行 XSS 攻击(不知道什么是 XSS 攻击?请出门百度/Google,再回来),有些同学可能会说在页面渲染的时候使用如下方式就好了:

{!! $article->content !!}

确实是这样,这是在前端避免 XSS 攻击的最终保障,但是在下次进入内容编辑页面时,是无法避免内容中 JavaScript 代码的执行的,比较恶心(恶心自己,哈哈,但是作为一个有情怀的平台,我们努力让你不恶心),万全之策是在提交内容保存到数据库之前对内容中的恶意代码进行过滤,我们可以通过 HTML Purifier 帮我们做这个事儿,HTML Purifier 本身是支持多语言的,作为一个避免 XSS 攻击的通用解决方案,自然已经有人在 PHP 版本基础上封装了适用于 Laravel 的扩展包 mews/purifier,在 Laravel 应用中使用这个扩展包可以让你在几分钟内就可以彻底解决 XSS 攻击的后顾之忧,只需简单几步即可。

首先是通过 Composer 安装这个扩展包:

composer require mews/purifier

针对 Laravel 5.5+ 版本,无需手动注册服务提供者,安装完成后通过以下命令发布配置文件 purifer.phpconfig 目录下:

php artisan vendor:publish --provider="Mews\Purifier\PurifierServiceProvider"

如果你有一些个性化的需求,可以编辑配置文件 purifer.php,我们可以在其中配置支持的 HTML 标签白名单以及标签属性白名单,如果你使用的是默认配置的话,它们分别位于 HTML.AllowedCSS.AllowedProperties 配置项中:

HTML Purifier 配置

这是一种白名单机制,不在白名单中的标签和属性在经过 HTML Purifier 「清洗」之后会被过滤掉,调用 HTML Purifier 对文本进行处理也很简单:

clean($request->post('content'));

或者:

Purifier::clean($request->post('content'));

经过 clean 方法「洗礼」之后,留下来的就是仅包含白名单中支持的标签属性的纯净文本,这样的文本存到数据库中,无论放到哪里去渲染,都不用再担心有 XSS 攻击的隐患。

关于富文本编辑器就简单说到这里,下一篇我们来聊聊程序员的首选写作语言 Markdown 及其预览、避免 XSS 攻击实现的解决方案。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 通过 Google 身份验证器为你的网站打造一个动态手机令牌

>> 下一篇: Laravel 学院文本编辑器及 XSS 攻击防范 —— Markdown 编辑器篇