博客

在Laravel中发布视图可能并不安全 - 怎样保证安全

Jun 26, 2022
Martin
Admin panel, Form builder

发布 Blade 模板视图,可能会给应用带来破坏性改变。

如果你之前用过 Laravel 包,你应该知道,它们很多时候使用 Blade 视图渲染内容。当包开发者注册 /views 目录时,事实上在引导 Laravel 到两个地方查找 Blade 视图。

想象一个场景,有一个包想要渲染 控制面板视图。包视图以包名前缀命名,因此开发者会这样使用视图:

view('filament::dashboard');

首先,Laravel 会搜索 /resources/views/vendor/filament 目录,寻找 dashboard 视图。这是你应用内的目录,你可以完全控制,不像其他 /vendor 目录下的文件。

如果 Laravel 在该目录中找不到 dashboard 视图,它就会搜索 /vendor 目录下的包视图目录。这些视图文件是用户不能编辑的,因为每当 composer update 运行更新,这些视图就会被重写覆盖。

这一结构非常强大。它让包用户可以随意重写视图,允许他们在应用中完全控制视图外观和功能。

为了让处理更加简单,Laravel 允许用户使用 vendor:publish 命令从 /vendor 目录中将视图复制到 /resources/views/vendor 目录下。看起来很简单,是吧。不过,经常也会过度使用。

为什么会这么危险?

Blade 视图不仅仅是一段内容、一个带有一些 CSS 类或图片的 <div> 代码块。2022 年此刻的 Blade,对 Laravel 开发者而言,已经特别强大。其中有很多有用的指令可用来在视图中运行 PHP 代码。同时 Livewire 也提供了在 Blade 中和后台代码交互的 API。

通过发布 Blade 视图,你假定了视图中的代码不会在包的主版本期间再有改变。

换句话说:当你运行 composer update 时,如果视图和包中的 PHP 类有交互行为或者数据直接传入视图中,那就发布的视图就会有破坏性风险。

  • 比如,在 v1.0 版本中,Dashboard 视图可能在 chart 图表中使用 $data 变量来读取数据数组。

  • 你想要改变 chart 外观并添加一些自定义 CSS 类。因此,你在应用中发布了视图并添加了一些额外代码。

  • 然后 v1.1 版本中,包维护者为 chart 图表添加了新特性:现在可以添加多种数据集到图表中了,因此,现在的 $data 是一个数组集合。

  • 你运行了 composer update,由于包遵循版本控制语义,安装了 v1.1 版本。

  • 你再访问 dashboard 时,出现了错误:$data 现在是数组集合,chart 图表完全被破坏了。

  • 你和其他发布视图的用户一起,上报了 Bug 给包维护者,他们花时间帮你调试这个问题,结果发现实际上只是你自己的问题。

更糟糕的是 - 由于 vendor:publish 被广泛用于一次性发布所有包视图,那些没有修改 Dashboard 视图的用户也经历了这个 Bug,而他们并不清楚为何会这样,因为他们可能甚至都不知道 dashboard 视图的存在。

另一个绊脚石: Blade 组件

Blade 组件为这一问题添加了一层复杂度,因为视图之间现在能够以一种炫酷的方式相互交互。然而,这也导致了属性中出现了一个新的问题 - 包维护者如果在已发布的 Blade 视图组件中添加了一个新的必须的属性,由于你并没有将该属性传入组件,就会导致抛出异常而应用崩溃。

保证安全

很明显,最简单的办法是,不要在第一现场发布视图。

有时,当面对问题时,我们通常会寻找最简单的方案。这种情况下,我们通常可能会发布视图并对其修改。下一次,是不是可以试试使用 CSS 自定义?许多这一类对包中视图的修改可以不用触碰视图文件,而只需添加一些额外的代码。这一问题的部分原因,可能在于 Blade 用户内心通常是后端开发者,希望尽可能少地触碰前端代码。我很理解这种想法,我也觉得 CSS 有时有点难;特别是如果你要重写原有的样式,你需要考虑特殊性时。不过,即使是 Tailwind 类,现在也可以通过 @apply 在 CSS 文件中使用,而且也能使用 !important。如果你曾发现需要在包中的视图里添加一些额外的类,请提交 PR !我想,包维护者会立即和接受你的修改,因为他们也想最小化他们调试你将来问题的时间。

然后如果你确实需要发布视图,请不要使用 vendor:publish。请直接访问 /vendor 目录,查找你需要改变的视图,并只将其复制到你的应用中。这样做就能绝对最小化将来包发布的影响,虽说仍然不是完全安全,但也会让风险更容易管理。

如果你想在发布视图的时候绝对安全,也可以在 compoer.json 中将包锁定到指定版本。比如:

"filament/filament": "^2.0"

改成这样:

"filament/filament": "v2.12.1"

然后,当有新的发布版本,手动在 composer.json 中碰撞版本,复核其中已发布视图的破坏性变更。

#我是包维护者,怎样让包的自定义更为安全呢? 首先 - 让你的包可以通过 CSS 进行自定义。如果你使用的是 Tailwind 或者其他通用类来组织 HTML, 可以添加一些包指定的类。比如 package-button 或者 package-dashboard-chart。不要害怕建议用户提交他们所需 CSS 类的 PR。鼓励他们通过这一途径而非发布视图来实现。

同时,在文档中删除 vendor:publish 中发布视图的命令。很多用户看到有这么一个命令,在安装包时就会不管有没有用直接运行。主动劝阻用户这么做。

另一种让用户减少发布风险的是,让视图更具颗粒度。通过让视图更小,减少了 PHP 代码的交互,可以让包的自定义更加安全。比如,将 search 视图分解成 search.input, search.icon, search.label, search.results.lists 和 search.resultes.card。用户会因此感谢你 😄