上线前最后一小时,我靠这张便签纸拦住了一个能直接删库的按钮

站长杂谈 14 浏览 0 回复 返回上级

上周帮朋友公司搭了一套后台管理系统,临上线前我照例掏出手机备忘录里那张"祖传便签"——不是信不过自己,是吃过太多"我以为关了"的亏。今天把这张清单摊开聊聊,权当给新手站长提个醒,老手站长找共鸣。

一、权限:别相信"默认就是安全的"

这次踩的坑特别蠢。RBAC 权限表导完测试数据,我顺手把 `admin` 角色的 `status` 设成了 1,却没检查测试账号里混进去一个 `id=1` 的"体验用户"。上线后如果被人扫到,直接继承超管权限。现在我的清单第一项固定是:逐条核对 role_id=1 的绑定关系,不是看代码,是进数据库 `SELECT` 出来肉眼过一遍。

还有文件权限。上次项目 `.env` 被 Nginx 裸奔访问,这次我加了条自动化脚本:部署完跑一圈 `find . -type f -perm /o+w`,但凡有其他人可写的文件,CI 直接报错。宝塔面板里那个"修复权限"按钮我早不信了,它修的是它自己的规范,不是你的业务规范。

二、路由:隐藏接口比 404 更可怕

ThinkPHP 的 `route/app.php` 这次加了二十多条路由,但最花时间的是删路由。开发阶段为了调试开的 `/test/reset` 直接清空缓存,还有 `/api/debug/sql` 这种裸奔接口,你以为注释掉控制器方法就安全了?路由注册还在,只是变成 500 而已,扫描器照样标记为"存在但异常"。

我的做法现在变成:上线前 `grep -r "Route::" route/` 导出全部路由,跟接口文档做差集。多出来的每一条,要么写进文档,要么从配置文件里物理删除。这次就抓到一个 `Route::get('user/export', ...)`,是早期给运营导数据用的,没鉴权,没限流,没日志。

三、菜单:前端看不见 ≠ 后端进不来

后台菜单这次用了动态渲染,权限表控制显示。但有个坑:我把"财务报表"菜单的显示权限关了,对应的 API 接口却没加校验。运营同事直接记了接口地址,浏览器里敲 URL 照样进。现在清单里专门有一栏:菜单权限与接口权限必须双向绑定,不是"同步",是"互相校验"。

具体操作上,我会在 `auth_rule` 表里加了个 `is_menu` 标记,上线前跑 SQL 检查:`SELECT api_rule FROM api_permissions WHERE api_rule NOT IN (SELECT name FROM auth_rule WHERE is_menu=0)`。漏网之鱼一目了然。

四、配置:生产环境是另一个物种

这次最惊险的是 OSS 配置。测试环境用的内网 Endpoint,生产环境切公网时,我 `.env` 里改了 `OSS_ENDPOINT`,却忘了 `OSS_CDN_DOMAIN` 还是测试站的加速域名。上线后用户上传图片全走测试 CDN,账单倒是小事,关键是测试站 CDN 配置了 IP 白名单,部分地区用户直接传失败。

现在我的 `.env` 文件分三块:通用配置、环境专属配置、上线必改项。第三块用 `### DEPLOY CHECK ###` 包起来,脚本部署时如果检测到这些 key 的值包含 `test` `dev` `localhost`,直接中断。粗暴,但管用。

还有数据库配置里的 `prefix`,这次从测试环境迁过来,有个插件硬编码了表名,没走 `config('database.prefix')`,上线后插件直接报 "Table doesn't exist"。这种属于代码债,但上线前能拦一道是一道——我的做法是全局搜 `Db::table('zs_` 这种硬编码前缀,发现一个修一个。

五、那张便签纸现在的样子

贴显示器边框上,发黄了,字越写越密:

• 权限:role_id=1 绑定、文件 o+w、.env 可读
• 路由:grep 导出差集、测试接口物理删除
• 菜单:菜单权限↔接口权限双向 SQL 校验
• 配置:### DEPLOY CHECK ### 拦截、硬编码前缀扫描
• 额外:错误页面关闭调试信息、SSL 强制跳转、备份脚本 cron 状态

最后一条是上次加的。有回上线顺利,第二天发现自动备份没跑,原来是宝塔计划任务里路径写错了,日志攒了三个月没报过错——因为错误日志也写到了那个不存在的路径里,完美闭环。

你们上线前有什么"祖传仪式"?我这张便签纸估计还能再贴两年。

评论0
回复 · 0
还没有回复
微信客服 微信客服