本文目录导读:

在PHP项目中记录异常信息时,对敏感数据进行脱敏处理是保护用户隐私和安全的重要措施,以下是几种常见的脱敏方案和实现方法:
基础脱敏方案
手机号脱敏
function maskPhone($phone) {
if (empty($phone)) return '';
return substr($phone, 0, 3) . '****' . substr($phone, -4);
}
// 188****8888
邮箱脱敏
function maskEmail($email) {
if (empty($email)) return '';
$parts = explode('@', $email);
$name = $parts[0];
$domain = $parts[1] ?? '';
$maskedName = substr($name, 0, 2) . str_repeat('*', strlen($name) - 2);
return $maskedName . '@' . $domain;
}
// ab***@example.com
身份证号脱敏
function maskIdCard($idCard) {
if (empty($idCard)) return '';
return substr($idCard, 0, 6) . '********' . substr($idCard, -4);
}
// 110101********1234
通用脱敏类
class DataMasker {
private static $patterns = [
'phone' => [
'pattern' => '/(1[3-9]\d)\d{4}(\d{4})/',
'replacement' => '$1****$2'
],
'email' => [
'pattern' => '/(\w{2})\w*@(\w+\.\w+)/',
'replacement' => '$1***@$2'
],
'id_card' => [
'pattern' => '/^(\d{6})\d{8}(\d{4})$/',
'replacement' => '$1********$2'
],
'bank_card' => [
'pattern' => '/^(\d{4})\d{8,12}(\d{4})$/',
'replacement' => '$1****$2'
],
'password' => [
'pattern' => '/password["\']?\s*[:=]\s*["\'].*?["\']/i',
'replacement' => 'password"***"'
],
'token' => [
'pattern' => '/token["\']?\s*[:=]\s*["\'].*?["\']/i',
'replacement' => 'token"***"'
]
];
public static function mask($data, $type = null) {
if ($type && isset(self::$patterns[$type])) {
return preg_replace(
self::$patterns[$type]['pattern'],
self::$patterns[$type]['replacement'],
$data
);
}
// 自动检测并脱敏
$result = $data;
foreach (self::$patterns as $type => $rule) {
$result = preg_replace($rule['pattern'], $rule['replacement'], $result);
}
return $result;
}
public static function maskArray($data, $fields = []) {
if (empty($fields)) {
$fields = ['phone', 'email', 'id_card', 'bank_card', 'password', 'token'];
}
foreach ($data as $key => &$value) {
if (in_array($key, $fields)) {
if (method_exists(self, 'mask' . ucfirst($key))) {
$value = self::{'mask' . ucfirst($key)}($value);
}
} elseif (is_array($value)) {
$value = self::maskArray($value, $fields);
}
}
return $data;
}
}
集成到日志系统
Monolog 处理器
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Processor\ProcessorInterface;
class SensitiveDataProcessor implements ProcessorInterface {
private $fields = ['password', 'token', 'credit_card', 'ssn'];
private $masker;
public function __construct() {
$this->masker = new DataMasker();
}
public function __invoke(array $record): array {
// 处理上下文中的敏感数据
if (isset($record['context'])) {
$record['context'] = DataMasker::maskArray($record['context'], $this->fields);
}
// 处理异常信息
if (isset($record['context']['exception'])) {
$record['context']['exception'] = $this->maskException($record['context']['exception']);
}
// 处理消息本身
$record['message'] = DataMasker::mask($record['message']);
return $record;
}
private function maskException($exception) {
if ($exception instanceof \Throwable) {
return [
'class' => get_class($exception),
'message' => DataMasker::mask($exception->getMessage()),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'trace' => $this->maskTrace($exception->getTraceAsString())
];
}
return $exception;
}
private function maskTrace($trace) {
// 脱敏栈追踪中的敏感信息
return DataMasker::mask($trace);
}
}
// 使用示例
$logger = new Logger('app');
$logger->pushProcessor(new SensitiveDataProcessor());
$logger->pushHandler(new StreamHandler('/var/log/app.log', Logger::DEBUG));
自定义日志包装器
class SafeLogger {
private $logger;
private $sensitiveKeys = ['password', 'token', 'secret', 'authorization'];
public function __construct($logger) {
$this->logger = $logger;
}
public function error($message, array $context = []) {
$safeMessage = $this->maskSensitiveData($message);
$safeContext = $this->maskContext($context);
$this->logger->error($safeMessage, $safeContext);
}
public function exception(\Throwable $e, array $context = []) {
$safeContext = $this->maskContext($context);
$safeMessage = DataMasker::mask($e->getMessage());
$this->logger->error($safeMessage, array_merge([
'exception' => [
'class' => get_class($e),
'message' => $safeMessage,
'code' => $e->getCode(),
'file' => $this->maskFilePath($e->getFile()),
'line' => $e->getLine()
]
], $safeContext));
}
private function maskSensitiveData($data) {
if (is_string($data)) {
return DataMasker::mask($data);
}
return $data;
}
private function maskContext(array $context) {
foreach ($context as $key => $value) {
if (in_array(strtolower($key), $this->sensitiveKeys)) {
$context[$key] = '***MASKED***';
} elseif (is_array($value)) {
$context[$key] = $this->maskContext($value);
}
}
return $context;
}
private function maskFilePath($file) {
// 脱敏用户目录路径
$homePath = getenv('HOME') ?: '/home';
return str_replace($homePath, '/home/***', $file);
}
}
框架集成示例
Laravel 集成
// app/Exceptions/Handler.php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use App\Logging\SensitiveDataProcessor;
class Handler extends ExceptionHandler
{
protected function context()
{
try {
$context = parent::context();
// 脱敏敏感数据
$processor = new SensitiveDataProcessor();
return $processor->__invoke(['context' => $context])['context'];
} catch (\Throwable $e) {
return [];
}
}
}
ThinkPHP 集成
// app/provider.php
return [
'think\Log' => [
'type' => 'File',
'path' => './runtime/log/',
'processor' => \app\common\SensitiveDataProcessor::class,
],
];
自定义脱敏规则
class SensitiveDataFilter {
private $rules = [];
public function addRule($pattern, $replacement) {
$this->rules[] = [
'pattern' => $pattern,
'replacement' => $replacement
];
}
public function filter($data) {
if (is_array($data)) {
return array_map([$this, 'filter'], $data);
}
$result = $data;
foreach ($this->rules as $rule) {
$result = preg_replace($rule['pattern'], $rule['replacement'], $result);
}
return $result;
}
}
// 使用示例
$filter = new SensitiveDataFilter();
$filter->addRule('/\b\d{16,19}\b/', '****-****-****-****'); // 信用卡号
$filter->addRule('/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/', '***@***.***'); // 邮箱
最佳实践建议
配置管理
class SensitiveConfig {
private static $instance;
private $config;
private function __construct() {
$this->config = require __DIR__ . '/sensitive_config.php';
}
public static function getInstance() {
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function getMaskRules() {
return $this->config['mask_rules'] ?? [];
}
public function getSensitiveFields() {
return $this->config['sensitive_fields'] ?? [];
}
}
// sensitive_config.php
return [
'mask_rules' => [
'phone' => '/(1[3-9]\d)\d{4}(\d{4})/',
'email' => '/(\w{2})\w*@(\w+\.\w+)/',
],
'sensitive_fields' => [
'password', 'token', 'secret', 'credit_card', 'ssn'
]
];
日志记录注意事项
- 分级记录:不同级别的日志记录不同详细程度的信息
- 定期审计:定期检查日志中是否包含未脱敏的敏感信息
- 开发环境:开发环境可以记录更多信息,但生产环境必须严格脱敏
- 错误码系统:使用错误码代替具体的错误信息
通过合理实现上述方案,可以有效保护用户隐私和系统安全,同时保证异常信息的可追溯性。