ThinkPHP模型层通过$_filter机制过滤敏感字段,防止数据意外暴露或篡改;但防越权需在中间件或控制器中校验权限与资源归属,模型过滤仅控制数据呈现。

ThinkPHP模型层怎么过滤敏感字段?
模型层本身不自动拦截越权访问,但能通过字段过滤机制防止数据被意外暴露或篡改。关键不是“拦住谁”,而是“不让不该读的字段被读出来、不该写的字段被写进去”。$_filter 是 ThinkPHP 3.2.3 高级模型(AdvModel)提供的核心能力,它在数据读取/写入时自动触发,比控制器里手动 unset() 更可靠。
-
$_filter必须定义在继承自Think\Model\AdvModel的模型类中,基础Model类不支持 - 写入过滤函数接收的是原始值(如用户传来的
password字符串),适合做加盐哈希;读取过滤函数也接收原始值,适合做脱敏(如把手机号138****1234) - 别在过滤函数里查数据库或调用耗时逻辑——它会在每次
find()、select()、add()时执行,影响性能 - 示例:隐藏管理员字段
is_admin的读取输出:protected $_filter = [ 'is_admin' => ['htmlspecialchars', function($val) { return $val ? '***' : 0; }] ];
为什么不能只靠模型过滤来防越权?
因为模型层过滤只是“数据呈现控制”,不是“权限决策点”。它解决的是“数据该不该这样展示”,而不是“这个人能不能看这条数据”。比如一个普通用户请求 /user/5,模型即使过滤了 balance 字段,只要控制器没校验用户身份和资源归属,攻击者仍可反复猜 ID 扫描他人记录。
- 越权访问的本质是业务逻辑漏洞,必须在控制器或中间件中做
auth+ownership双重判断 - 模型过滤无法阻止 SQL 查询本身——如果控制器写了
UserModel::where('id', input('id'))->find()却没校验当前用户是否拥有该id,过滤再严也没用 - ThinkPHP 6 中推荐用验证器 + 中间件组合:先用中间件校验路由权限(如角色),再用验证器确保参数合法,最后在查询前加归属条件(如
where('user_id', session('uid')))
ThinkPHP 6 怎么在中间件里做权限拦截?
中间件是 ThinkPHP 6 防越权最自然的位置,因为它在控制器执行前就介入,能统一拦截非法请求,避免重复写校验逻辑。
- 中间件文件放在
app/middleware/目录下,handle()方法里用$request->param()拿参数,用$request->session()拿登录态 - 别直接在中间件里查数据库判断权限——应提前把用户权限信息(如角色、可操作资源 ID 列表)缓存到 session 或 Redis,否则每个请求都查库会拖慢响应
- 路由绑定中间件时,优先用分组方式,避免全局注册导致非敏感接口也被强制校验:
Route::group(['middleware' => 'CheckResource'], function () { Route::get('order/:id', 'Order/detail'); }); - 拦截后返回 JSON 错误时,状态码要用
403 Forbidden而不是401 Unauthorized——前者表示“你有身份,但没权限”,后者是“你没登录”,语义更准确
序列化字段($serializeField)会不会带来越权风险?
会,而且容易被忽略。序列化字段把多个逻辑字段塞进一个数据库字段(如 info),读取时自动反序列化成对象属性。但如果没控制好读写范围,可能让本不该暴露的子字段(如 admin_note)被一并吐出。
立即学习“PHP免费学习笔记(深入)”;
-
$serializeField仅在AdvModel中生效,且只对定义的字段名做自动序列化/反序列化,不校验内容合法性 - 如果前端提交了额外字段(如
info[admin_only]=xxx),而模型没限制反序列化白名单,就可能被注入恶意键值 - 安全做法:序列化前用
array_intersect_key()过滤掉非预期键名;读取后对敏感子字段(如info['secret'])再走一次$_filter或手动unset() - 更稳妥的替代方案:不用序列化字段,拆成独立字段或用 JSON 类型(MySQL 5.7+ / PostgreSQL),配合数据库层面的列权限控制
where('id', input('id')) 后面没加 and user_id = ?。这点不补上,其他所有过滤都只是掩耳盗铃。



















