codeigniter 4需通过应用层拦截、自定义masker类及输出前过滤实现数据脱敏,脱敏应置于controller或service层,json接口须序列化前处理,严禁修改原始字段与数据库查询,masker类统一管理策略并支持环境/角色开关。

CodeIgniter 4 本身不提供开箱即用的数据脱敏功能,但可以通过应用层拦截 + 自定义工具类 + 输出过滤的组合方式,在不影响原有逻辑的前提下安全地脱敏敏感字段。关键不是“CI4有没有脱敏”,而是“你在哪里、对谁、以什么粒度做脱敏”。
脱敏该放在 Controller 还是 View 层?
优先放在 Controller 层(或更早的 Service/Model 层),避免 View 中混入业务逻辑和敏感规则。View 只负责渲染,不该知道手机号要不要掩码、身份证第几位该隐藏。
- Controller 中调用
maskMobile()或Masker::idCard($id)处理后,再传给 View —— 明确、可测、易复用 - 若在 View 中用
str_replace()或正则硬写,会导致脱敏逻辑分散、无法统一开关、测试困难 - 特别注意:JSON API 接口(如返回
json_encode($data))必须在序列化前完成脱敏,否则前端拿到原始值就晚了
如何避免脱敏影响数据库查询和关联?
脱敏只应作用于「输出」,绝不修改数据库原始值或查询条件。常见错误是把 $user->mobile = maskMobile($user->mobile) 写在 Model 的 find() 之后,却忘了后续要用这个字段做短信发送或权限校验。
- 永远保留原始字段名(如
mobile、id_card)用于业务逻辑;脱敏后建议用新键名(如mobile_masked、id_card_masked)输出 - 不要在
select()查询中直接写CONCAT(LEFT(mobile,3),"****") as mobile—— 这会让 ORM 失效,且无法复用脱敏策略(比如测试环境要全显,生产才掩码) - 若需动态控制脱敏开关(如管理员可见全量),建议通过请求上下文(
service('auth')->user()->isSuperAdmin())判断,而不是改 SQL
怎样让脱敏规则集中管理、支持切换?
建一个轻量级 App\Libraries\Masker 类,用静态方法封装常用脱敏逻辑,并允许按环境或角色启用/禁用。
class Masker
{
public static function mobile(string $value): string
{
if (env('APP_ENV') === 'development' || auth()->user()?->can('view_full_mobile')) {
return $value;
}
return preg_replace('/(\d{3})\d{4}(\d{4})/', '$1****$2', $value);
}
public static function idCard(string $value): string
{
if (strlen($value) === 18) {
return substr($value, 0, 4) . '****' . substr($value, -4);
}
return $value;
}
}
- 所有脱敏入口统一走
Masker::mobile(),替换策略、长度判断、空值处理都在一处维护 - 避免在多个 Controller 里重复写
preg_replace(),也避免用全局配置数组存正则(难调试、无类型提示) - 注意:脱敏函数必须能安全处理 null / empty / malformed 字符串,否则 500 错误会暴露原始数据
为什么不能依赖数据库层脱敏(如 MySQL CONCAT)?
因为 CI4 的 Query Builder 和 Result 集成深度绑定,一旦你在 select() 里写表达式,$result->getRow() 返回的对象属性名就不再是原始字段名,容易引发隐性 bug。
- 例如:
$builder->select("id, CONCAT(LEFT(mobile,3),'****') as mobile")→$row->mobile是脱敏后的,但$row->original_mobile不存在,业务代码可能误用 - 数据库层脱敏还绕过了 PHP 层的权限判断(比如客服只能看部分字段,DB 不知道当前用户是谁)
- 当需要对接 Redis 缓存、ES 搜索、消息队列时,脱敏逻辑必须在 PHP 层重写一遍,违背 DRY 原则
真正容易被忽略的是脱敏与缓存的耦合:如果用 cache()->save('user_123', $user) 存了已脱敏的数据,下次管理员查时取出来的还是掩码版。所以缓存键设计或脱敏时机,必须和用户身份强绑定——这不是加个函数就能解决的事。











