Laravel中敏感字段脱敏需全路径覆盖:模型访问器统一处理(如phone_masked)、API资源各层级独立脱敏(推荐复用模型访问器)、集合map需逐层穿透脱敏(如用户→文章→评论→评论者邮箱)。

在Laravel项目中处理用户敏感字段(如手机号、身份证号、邮箱)时,嵌套查询(如with()关联加载、集合map()嵌套遍历、API资源中的嵌套关系)容易遗漏脱敏逻辑,导致原始数据意外暴露。必须在数据离开控制器前完成全路径脱敏,而非仅在最终返回前做一次字符串替换。
模型访问器统一脱敏
在对应Eloquent模型中定义访问器,对敏感字段自动脱敏。此方式覆盖所有查询路径(包括select()、with()、toArray()、toJson()),无需每次手动调用。
在App\Models\User.php中添加:
protected $appends = ['phone_masked', 'id_card_masked'];
public function getPhoneMaskedAttribute()
{
return $this->phone ? substr($this->phone, 0, 3) . '****' . substr($this->phone, -4) : null;
}
public function getIdCardMaskedAttribute()
{
return $this->id_card ? substr($this->id_card, 0, 6) . '******' . substr($this->id_card, -4) : null;
}
注意:访问器名必须以Attribute结尾,且不能与数据库字段同名,否则会触发无限递归。
API资源中嵌套关系脱敏
当使用ApiResource返回带关联的数据(如用户+其订单列表+订单收货人信息)时,需确保每个嵌套层级的敏感字段都被处理。不能只在UserResource里脱敏,还要在OrderResource、AddressResource中各自定义脱敏逻辑。
方法一:在子资源中定义独立访问器
在App\Http\Resources\OrderResource.php中:
public function toArray($request)
{
return [
'id' => $this->id,
'receiver_phone' => $this->receiver_phone ? substr($this->receiver_phone, 0, 3) . '****' . substr($this->receiver_phone, -4) : null,
'created_at' => $this->created_at,
];
}
方法二:复用模型访问器(推荐)
先在Order模型中定义getReceiverPhoneMaskedAttribute(),再在OrderResource中直接引用:
'receiver_phone' => $this->receiver_phone_masked,
【receiver_phone_masked必须已在Order模型中声明为$appends项】
集合批量脱敏(含深层嵌套)
当使用collect()->map()或$users->map()进行多层嵌套转换时,需逐级穿透脱敏。例如:用户→文章→评论→评论者邮箱。
第一步:获取原始数据
$users = User::with(['posts.comments.user'])->get();
第二步:使用map深度脱敏
return $users->map(function ($user) {
return $user->only(['id', 'name']) + [
'phone' => $user->phone_masked,
'posts' => $user->posts->map(function ($post) {
return $post->only(['id', 'title']) + [
'comments' => $post->comments->map(function ($comment) {
return $comment->only(['id', 'content']) + [
'user_email' => $comment->user->email_masked ?? null,
];
}),
];
}),
];
});


















