本文目录导读:

在PHP中实现动态脱敏(Dynamic Data Masking)通常指在不修改原始数据的情况下,根据用户权限或场景动态展示掩码后的数据,以下提供几种常见实现方式:
基础脱敏函数(通用掩码规则)
class DataMasker {
/**
* 手机号脱敏:保留前3后4
*/
public static function mobile(string $mobile): string {
if (strlen($mobile) !== 11) {
return $mobile;
}
return substr_replace($mobile, '****', 3, 4);
}
/**
* 身份证脱敏:保留前4后4
*/
public static function idCard(string $id): string {
if (strlen($id) < 8) {
return $id;
}
return substr_replace($id, str_repeat('*', strlen($id) - 8), 4, -4);
}
/**
* 邮箱脱敏:用户名部分@前保留首字母,其余掩码
*/
public static function email(string $email): string {
$parts = explode('@', $email);
if (count($parts) !== 2) {
return $email;
}
$name = $parts[0];
$domain = $parts[1];
if (strlen($name) <= 2) {
$maskedName = substr($name, 0, 1) . str_repeat('*', strlen($name) - 1);
} else {
$maskedName = substr($name, 0, 1) . str_repeat('*', strlen($name) - 2) . substr($name, -1);
}
return $maskedName . '@' . $domain;
}
}
// 使用示例
echo DataMasker::mobile('13812345678'); // 138****5678
echo DataMasker::idCard('110101199001011234'); // 1101**********1234
echo DataMasker::email('testuser@gmail.com'); // t******r@gmail.com
基于策略模式的动态脱敏(支持配置)
interface MaskingStrategy {
public function mask($value): string;
}
class MobileStrategy implements MaskingStrategy {
public function mask($value): string {
return substr_replace($value, '****', 3, 4);
}
}
class EmailStrategy implements MaskingStrategy {
public function mask($value): string {
$parts = explode('@', $value);
$name = $parts[0];
$maskedName = substr($name, 0, 1) . str_repeat('*', max(0, strlen($name) - 2)) . substr($name, -1);
return $maskedName . '@' . ($parts[1] ?? '');
}
}
class DynamicMasker {
private array $strategies = [];
public function __construct(array $config = []) {
// 默认策略配置
$this->strategies = $config ?: [
'mobile' => new MobileStrategy(),
'email' => new EmailStrategy(),
];
}
public function maskField(string $field, $value): string {
if (isset($this->strategies[$field])) {
return $this->strategies[$field]->mask($value);
}
return $value; // 未配置策略则返回原值
}
public function maskArray(array $data, array $rules): array {
foreach ($rules as $field => $shouldMask) {
if ($shouldMask && isset($data[$field])) {
$data[$field] = $this->maskField($field, $data[$field]);
}
}
return $data;
}
}
// 使用示例
$masker = new DynamicMasker();
$user = [
'name' => '张三',
'mobile' => '13812345678',
'email' => 'zhangsan@example.com'
];
$masked = $masker->maskArray($user, [
'name' => false, // 不脱敏
'mobile' => true, // 脱敏
'email' => true // 脱敏
]);
// 结果: ['name' => '张三', 'mobile' => '138****5678', 'email' => 'z******n@example.com']
基于注解/AOP的自动脱敏(高级应用)
#[Attribute(Attribute::TARGET_PROPERTY)]
class Sensitive {
public string $type;
public function __construct(string $type = 'default') {
$this->type = $type;
}
}
class User {
#[Sensitive('name')]
public string $name;
#[Sensitive('mobile')]
public string $mobile;
#[Sensitive('email')]
public string $email;
public function __construct(string $name, string $mobile, string $email) {
$this->name = $name;
$this->mobile = $mobile;
$this->email = $email;
}
}
class SensitiveSerializer {
private array $maskers = [
'mobile' => [DataMasker::class, 'mobile'],
'email' => [DataMasker::class, 'email'],
'name' => function($name) {
return mb_substr($name, 0, 1) . str_repeat('*', mb_strlen($name) - 1);
}
];
public function serialize(object $object, array $context = []): array {
$reflection = new ReflectionClass($object);
$result = [];
foreach ($reflection->getProperties() as $property) {
$attributes = $property->getAttributes(Sensitive::class);
$value = $property->getValue($object);
if (!empty($attributes) && isset($this->maskers[$attributes[0]->newInstance()->type])) {
$masker = $this->maskers[$attributes[0]->newInstance()->type];
$result[$property->getName()] = $masker($value);
} else {
$result[$property->getName()] = $value;
}
}
return $result;
}
}
// 使用示例
$user = new User('张三', '13812345678', 'zhangsan@example.com');
$serializer = new SensitiveSerializer();
$maskedData = $serializer->serialize($user);
// 结果: ['name' => '张*', 'mobile' => '138****5678', 'email' => 'z******n@example.com']
数据库查询时动态脱敏(MySQL原生函数)
如果使用MySQL 8.0+,可以在SQL层面动态脱敏:
// MySQL查询时脱敏(示例查询)
$sql = "SELECT
id,
name,
CONCAT(LEFT(mobile,3), '****', RIGHT(mobile,4)) AS masked_mobile,
CONCAT(LEFT(email,1), '*****', SUBSTRING_INDEX(email, '@', -1)) AS masked_email
FROM users";
// 或者使用CASE WHEN进行权限控制
$sql = "SELECT
id,
name,
CASE WHEN :role = 'admin' THEN mobile ELSE CONCAT(LEFT(mobile,3), '****', RIGHT(mobile,4)) END AS mobile,
CASE WHEN :role = 'admin' THEN email ELSE CONCAT(LEFT(email,1), '****', SUBSTRING_INDEX(email, '@', -1)) END AS email
FROM users";
完整动态脱敏类(支持多种数据格式)
class DynamicDataMasker {
/**
* 根据类型掩码
*/
public static function mask($value, string $type = 'default'): string {
$value = (string)$value;
return match ($type) {
'mobile' => self::maskMobile($value),
'phone' => self::maskPhone($value),
'email' => self::maskEmail($value),
'id_card' => self::maskIdCard($value),
'bank_card' => self::maskBankCard($value),
'address' => self::maskAddress($value),
'name' => self::maskChineseName($value),
'password' => '******',
default => $value,
};
}
private static function maskMobile(string $mobile): string {
return strlen($mobile) === 11
? substr_replace($mobile, '****', 3, 4)
: $mobile;
}
private static function maskPhone(string $phone): string {
// 固定电话:保留区号和后4位
if (strlen($phone) >= 7) {
return substr($phone, 0, 4) . '****' . substr($phone, -4);
}
return $phone;
}
private static function maskEmail(string $email): string {
$parts = explode('@', $email);
if (count($parts) !== 2) return $email;
$name = $parts[0];
$domain = $parts[1];
$nameLen = strlen($name);
if ($nameLen <= 2) {
return substr($name, 0, 1) . str_repeat('*', $nameLen - 1) . '@' . $domain;
}
return $name[0] . str_repeat('*', $nameLen - 2) . $name[$nameLen - 1] . '@' . $domain;
}
private static function maskIdCard(string $id): string {
if (strlen($id) < 10) return $id;
return substr($id, 0, 4) . str_repeat('*', strlen($id) - 8) . substr($id, -4);
}
private static function maskBankCard(string $card): string {
if (strlen($card) < 8) return $card;
return substr($card, 0, 4) . ' **** **** ' . substr($card, -4);
}
private static function maskAddress(string $address): string {
// 只保留省市区,详细地址掩码
$pattern = '/(省|市|区|县)/u';
if (preg_match($pattern, $address, $matches, PREG_OFFSET_CAPTURE)) {
$pos = $matches[0][1] + mb_strlen($matches[0][0]);
return mb_substr($address, 0, $pos) . '****';
}
return $address;
}
private static function maskChineseName(string $name): string {
$len = mb_strlen($name, 'UTF-8');
if ($len == 1) return '*';
if ($len == 2) return mb_substr($name, 0, 1, 'UTF-8') . '*';
return mb_substr($name, 0, 1, 'UTF-8') . str_repeat('*', $len - 2) . mb_substr($name, -1, 1, 'UTF-8');
}
}
// 批量脱敏
class BatchDataMasker {
public static function maskBatch(array $data, array $fieldTypes): array {
$result = [];
foreach ($data as $key => $value) {
if (isset($fieldTypes[$key])) {
$result[$key] = DynamicDataMasker::mask($value, $fieldTypes[$key]);
} else {
$result[$key] = $value;
}
}
return $result;
}
}
// 使用示例
$userData = [
'name' => '张三丰',
'mobile' => '13912345678',
'email' => 'zhangsf@example.com',
'id_card' => '110101199001011234',
'address' => '北京市朝阳区建国路88号'
];
$fieldTypes = [
'name' => 'name',
'mobile' => 'mobile',
'email' => 'email',
'id_card' => 'id_card',
'address' => 'address'
];
$maskedData = BatchDataMasker::maskBatch($userData, $fieldTypes);
// 结果: ['name' => '张*丰', 'mobile' => '139****5678', ...]
最佳实践建议
- 配置驱动:将脱敏规则定义为配置文件,便于动态调整
- 权限分离:不同角色看到不同级别的脱敏数据
- 日志审计:记录脱敏前的原始数据操作日志
- 性能考虑:对大数组使用PDO的fetch模式或分批处理
- 国际化支持:处理多语言字符时使用mb_系列函数
- 防止过度脱敏:对短字符串(如2位数字)仅显示部分字符
选择哪种方案取决于你的具体需求:
- 简单项目:使用基础函数
- 复杂业务:使用策略模式+注解
- 高安全要求:结合数据库视图+ORM层面脱敏
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播