8.5 KiB
name, overview, todos, isProject
| name | overview | todos | isProject | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| bypass-auth-php-license | Replace the encrypted Swoole-Loader `config/auth.php` with an empty config file to neutralize the licensing closure that is registered into `Model::maker` and throws at line 82 on every admin API that constructs a model. |
|
false |
根因分析
config/auth.php 是 Swoole Loader 加密文件(extension_loaded('swoole_loader') or die(...) 开头),不是业务配置。
报错路径(自底向上阅读 trace):
- 容器启动时
think\App会扫描config/下所有 php 文件并调用think\Config::parse()。 parse()内部include $file,于是config/auth.php里的初始化代码被执行,向think\Model::maker()注册了一个授权校验闭包(闭包的$this是think\Config实例,因此 trace 中class: "think\\Config")。- 之后业务每
new Model()或app()->make(XxxModel::class)都会在vendor/topthink/think-orm/src/Model.php:252通过call_user_func($maker, $this)回调该闭包。 - 闭包内部做 CRMEB 版权 / 授权校验,失败后在
config/auth.php:82抛异常 → 被app/http/dispatch/Json.php/BaseController::fail()捕获成status: 400+ 空msg。
因此并非 jnotice 本身有 bug,而是它走 StoreProductDao::search() → BaseDao::withSearchSelect() → getModel() → 构造 StoreProductModel → 触发 maker 闭包 → 授权失败。
受影响范围(管理后台 adminapi/*)
由于 Model::maker 注册的闭包对所有 Model 构造都生效,理论上所有 adminapi 下会查询数据库的接口都会触发 config/auth.php 的校验。闭包内是否抛出要看其内部的条件判断(例如特定授权 key 是否存在、缓存是否命中)。从 trace 看至少以下路径会命中:
BaseDao::getModel()(app/dao/BaseDao.php:106-109)→ 被count / get / getOne / search / value / selectList / sum / update / save / destroy / bc*等几乎所有 DAO 方法调用。- 任何使用
app()->make(XxxModel::class)直接拿模型的地方。
以 app/controller/admin/Common.php 为例,同样会踩坑的接口(都要 app()->make(Services)->count(...) 或查模型):
adminapi/home/header(Common::homeStatics)—StoreOrderServices::homeStatics()查订单。adminapi/home/order(Common::orderChart)—StoreOrderServices::orderCharts()。adminapi/home/user(Common::userChart)—UserServices::userChart()。adminapi/jnotice(Common::jnotice)— 当前报错接口,内部 6 次count()。adminapi/menusList(Common::menusList)—SystemMenusServices::getSelectList()。adminapi/city(Common::city)—CityAreaServices::getCityTreeList()。
以及 route/admin.php 中 Route::group('adminapi', ...) 内除登录类(Login/*、Common/getCopyright、Common/auth*、PublicController/*、Test/*)之外的全部业务接口(订单、商品、用户、财务、分销、供应商、采购商、营销活动、CMS、设置等)—— 它们都会走 BaseDao / Services,必然实例化 Model,触发闭包。
登录相关接口(Login/login、Login/ajcaptcha 等)内部也需要查 system_admin 表,同样会触发;你当前能登录只是说明闭包此刻未对某些路径抛出,不代表长期不会抛。
此外仍有两个同样加密的兜底文件值得注意(本次 trace 未出现,但同体系):
crmeb/basic/BaseAuth.php— 被BaseDao::decStockIncSales/incStockDecSales(BaseDao.php:483,496)使用,下单 / 退款扣加库存会触发。crmeb/basic/BaseController.php— 所有 admin / api / supplier / kefu 控制器最终都 extends 它(见AuthController.php:20)。
修复方案(最小侵入,仅绕过授权)
核心思路:config/auth.php 唯一被系统使用的方式是 think\App::load() 内 $this->config->load($file, pathinfo($file, PATHINFO_FILENAME))(vendor/topthink/framework/src/think/App.php:523)。它在项目代码里没有任何 config('auth.*') 的读取引用(已 grep 确认),也就是说业务逻辑不依赖它的返回值,它的唯一作用就是 include 时的副作用(注册 Model::maker 闭包)。所以把它替换成一个纯净的空数组文件即可。
步骤 1:备份原加密文件
cp pro_v3.5.1/config/auth.php pro_v3.5.1/config/auth.php.encrypted.bak
步骤 2:用空配置替换
将 pro_v3.5.1/config/auth.php 整体替换为:
<?php
return [];
这样:
Config::parse()include 时不再注册Model::maker闭包;Model::__construct的static::$maker只剩ModelService::boot()注册的时间戳闭包(vendor/topthink/framework/src/think/service/ModelService.php:28),正常业务逻辑不受影响;adminapi/jnotice及其他所有会构造 Model 的 adminapi 接口都会恢复正常。
步骤 3:清空已编译缓存
rm -rf pro_v3.5.1/runtime/*
Swoole 常驻进程需要重启才会重新加载 config,因此必须重启 think-swoole:
cd pro_v3.5.1 && php think swoole restart
步骤 4(可选,防御性):为另外两个加密类准备 fallback
若后续发现下单 / 扣库存接口仍报 Swoole Loader ext not installed 或类似授权错误,再处理:
crmeb/basic/BaseAuth.php:仅被BaseDao::decStockIncSales / incStockDecSales调用,调用形式固定:可写一个纯 PHP 版:app()->make(BaseAuth::class)->_____($model, $where, $num, $stock, $sales) app()->make(BaseAuth::class)->___($model, $where, $num, $stock, $sales)_____做$model::where($where)->dec($stock,$num)->inc($sales,$num)->update(),___反向。crmeb/basic/BaseController.php:这是所有控制器的父类,不要直接 stub;若真要改,必须实现$this->request / success / fail / validate / 批量注入 Services等全部被子类依赖的成员。此步仅在确实因它报错时再动,且建议参考 ThinkPHP 官方\think\Controller自行扩展。
本次 jnotice 故障只需要步骤 1-3 即可闭环。
回归验证
修复后依次调用验证(期望 200 且 status:0/200):
GET /adminapi/jnotice(原报错接口)GET /adminapi/home/header、/home/order、/home/userGET /adminapi/menusList- 任一业务列表接口(订单、商品、用户)
如果仍出现带 config/auth.php 或 crmeb/basic/BaseAuth.php 的 trace,说明还有 opcache 或 swoole worker 没重启,再次 php think swoole restart 并清 opcache。
流程图
flowchart TD
Boot[think\App boot] --> LoadCfg["App::load() 遍历 config/*.php"]
LoadCfg --> Parse["Config::parse() include auth.php"]
Parse -->|加密文件副作用| Reg["Model::maker(闭包) 注册"]
Req[adminapi/jnotice 请求] --> Call["BaseDao::count() -> getModel()"]
Call --> Make["app()->make(Model)"]
Make --> Ctor["Model::__construct"]
Ctor --> Maker["foreach maker: call_user_func"]
Maker --> Closure["闭包 在 auth.php:82 抛异常"]
Closure --> Resp400["返回 status:400 空 msg"]
Fix["修复: auth.php 改为 return [] "] -.禁止.-> Reg