必须脱敏的参数包括password、password_confirmation、token、api_key、secret、card_number、cvv、id_number等;常见暴露点有Log::debug($request->all())、未过滤的中间件日志、第三方包捕获的原始输入及异常追踪中泄露的请求数据。

哪些参数必须脱敏?看 Laravel 日志里实际泄露的内容
默认情况下,Laravel 的 Log::info() 或异常日志中若记录了请求数据(比如 $request->all()),会原样输出 password、token、card_number、id_number 等字段。这不是理论风险——真实日志里常能看到 "password": "123456" 这种明文。
关键不是“要不要脱敏”,而是“哪些字段在哪些日志位置会被打出来”。常见暴露点包括:
- 使用
Log::debug($request->all())记录完整请求体 - 自定义中间件中未过滤就写入日志的
$request->query()或$request->post() - 第三方包(如
laravel-query-monitor)开启详细日志后自动捕获的原始输入 - 异常报告中通过
report()方法传入的$exception->getTraceAsString()间接带出请求数据
用 request()->except() 快速过滤敏感字段
最轻量、最可控的方式是手动剥离——尤其适合调试阶段或单个接口临时处理。不要直接 Log::info($request->all()),改用:
Log::info('API request', $request->except(['password', 'password_confirmation', 'token', 'api_key']));
注意几个细节:
-
$request->except()返回的是array,不是Request对象,不能链式调用->input() - 字段名区分大小写,
'Password'和'password'是两个键,建议统一按 API 实际接收的 key 写 - 如果请求含嵌套结构(如 JSON body 中的
{"user": {"password": "xxx"}}),except()不递归处理,需先json_decode($request->getContent(), true)后手动过滤 - GET 参数和 POST 参数混在同一数组时,
except()一并过滤,无需分开处理
全局拦截:在中间件中统一脱敏所有入参日志
当项目有多个 API 接口且都需日志审计时,硬编码 except() 易遗漏。推荐建一个日志脱敏中间件:
php artisan make:middleware SanitizeRequestForLogging
在 handle() 中修改日志上下文,而不是改原始请求:
public function handle($request, Closure $next)
{
$sensitiveKeys = ['password', 'token', 'secret', 'card_number', 'cvv', 'id_number'];
$logContext = $request->all();
foreach ($sensitiveKeys as $key) {
if (array_key_exists($key, $logContext)) {
$logContext[$key] = '[REDACTED]';
}
}
Log::info('API request', $logContext);
return $next($request);
}
这个中间件要放在路由组最外层(比如 api 中间件组顶部),否则可能错过某些前置逻辑的日志点。另外注意:
- 别在中间件里调用
$request->getContent()多次——Laravel 的 input 流只能读一次,重复读会返回空 - 如果用了
form_params(如 Postman 发 x-www-form-urlencoded),$request->all()能覆盖;但如果是 raw JSON,需提前用$request->json()->all()替代 - 脱敏后的
[REDACTED]值要能被运维快速识别为“已处理”,避免误判为业务异常值
环境隔离:开发/测试可全量,生产必须强制脱敏
本地调试时想看完整参数很正常,但上线后绝不能依赖人肉检查。利用 Laravel 的环境配置做开关:
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
'tap' => [App\Logging\SanitizeApiLogs::class], // ✅ 注入脱敏处理器
],
],
再新建 app/Logging/SanitizeApiLogs.php:
class SanitizeApiLogs implements \Illuminate\Contracts\Foundation\Application
{
public function __invoke($logger)
{
if (app()->environment('production')) {
$logger->listen(function ($level, $message, $context) {
$sensitive = ['password', 'token', 'api_key'];
foreach ($sensitive as $key) {
if (isset($context[$key])) {
$context[$key] = '[REDACTED]';
}
}
// 重写 context,不影响原始日志逻辑
return [$level, $message, $context];
});
}
}
}
这种基于 tap 的方式不侵入业务代码,且只在生产环境生效。容易被忽略的一点是:tap 回调里的 $context 是引用传递,但 Laravel 日志底层做了深拷贝,所以必须显式返回新数组才能生效。


















