本文目录导读:

我来介绍几种PHP数据脱敏显示的方案,包括常见的数据类型脱敏方法。
通用脱敏函数
<?php
class DataMasking
{
/**
* 手机号脱敏
* 保留前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 $idCard): string
{
if (strlen($idCard) < 8) {
return $idCard;
}
return substr_replace($idCard, '**********', 4, 10);
}
/**
* 姓名脱敏
* 保留姓,名字用*代替
*/
public static function name(string $name): string
{
if (mb_strlen($name) <= 1) {
return $name;
}
$first = mb_substr($name, 0, 1);
$rest = str_repeat('*', mb_strlen($name) - 1);
return $first . $rest;
}
/**
* 邮箱脱敏
* 保留前3位和域名
*/
public static function email(string $email): string
{
$atPos = strpos($email, '@');
if ($atPos === false) {
return $email;
}
$username = substr($email, 0, $atPos);
$domain = substr($email, $atPos);
if (strlen($username) <= 3) {
return '***' . $domain;
}
return substr($username, 0, 3) . str_repeat('*', strlen($username) - 3) . $domain;
}
/**
* 银行卡号脱敏
* 保留前4位和后4位
*/
public static function bankCard(string $cardNo): string
{
if (strlen($cardNo) <= 8) {
return $cardNo;
}
$masked = str_repeat('*', strlen($cardNo) - 8);
return substr($cardNo, 0, 4) . $masked . substr($cardNo, -4);
}
/**
* 地址脱敏
* 保留前6个字符
*/
public static function address(string $address): string
{
if (mb_strlen($address) <= 6) {
return $address;
}
return mb_substr($address, 0, 6) . str_repeat('*', mb_strlen($address) - 6);
}
/**
* IP地址脱敏
*/
public static function ip(string $ip): string
{
$parts = explode('.', $ip);
if (count($parts) !== 4) {
return $ip;
}
return $parts[0] . '.*.*.' . $parts[3];
}
/**
* 通用脱敏方法
* @param string $value 原始值
* @param string $type 类型:mobile, email, name, id_card, bank_card, address, ip
*/
public static function mask(string $value, string $type): string
{
switch ($type) {
case 'mobile':
return self::mobile($value);
case 'email':
return self::email($value);
case 'name':
return self::name($value);
case 'id_card':
return self::idCard($value);
case 'bank_card':
return self::bankCard($value);
case 'address':
return self::address($value);
case 'ip':
return self::ip($value);
default:
return $value;
}
}
}
// 使用示例
echo DataMasking::mobile('13812345678'); // 138****5678
echo DataMasking::name('张三丰'); // 张**
echo DataMasking::email('test@example.com'); // tes*****@example.com
基于注解的脱敏方案
<?php
/**
* 脱敏注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class MaskAttribute
{
public function __construct(
public string $type = 'default', // 脱敏类型
public ?int $start = null, // 开始位置
public ?int $length = null, // 脱敏长度
public string $maskChar = '*', // 掩码字符
public bool $keepEnd = true // 保留末尾字符
) {}
}
/**
* 脱敏处理器
*/
class MaskHandler
{
public static function handle(object $object)
{
$reflection = new \ReflectionClass($object);
foreach ($reflection->getProperties() as $property) {
$attributes = $property->getAttributes(MaskAttribute::class);
if (empty($attributes)) {
continue;
}
$maskAttribute = $attributes[0]->newInstance();
$property->setAccessible(true);
$value = $property->getValue($object);
if ($value !== null) {
$maskedValue = self::maskValue($value, $maskAttribute);
$property->setValue($object, $maskedValue);
}
}
}
private static function maskValue($value, MaskAttribute $attr): string
{
if ($attr->start !== null) {
return self::maskPart($value, $attr->start, $attr->length, $attr->maskChar, $attr->keepEnd);
}
// 根据类型脱敏
switch ($attr->type) {
case 'mobile':
return DataMasking::mobile($value);
case 'name':
return DataMasking::name($value);
case 'email':
return DataMasking::email($value);
case 'id_card':
return DataMasking::idCard($value);
default:
return (string)$value;
}
}
private static function maskPart($value, int $start, ?int $length, string $maskChar, bool $keepEnd): string
{
$str = (string)$value;
$len = strlen($str);
if ($start >= $len) {
return $str;
}
$maskLen = $length ?? ($keepEnd ? $len - $start - 1 : $len - $start);
if ($maskLen <= 0) {
return $str;
}
$mask = str_repeat($maskChar, $maskLen);
if ($keepEnd) {
return substr($str, 0, $start) . $mask . substr($str, $start + $maskLen, 1);
}
return substr($str, 0, $start) . $mask . substr($str, $start + $maskLen);
}
}
// 使用示例
class User
{
#[MaskAttribute(type: 'name')]
public string $name;
#[MaskAttribute(type: 'mobile')]
public string $phone;
#[MaskAttribute(type: 'email')]
public string $email;
#[MaskAttribute(start: 4, length: 4, keepEnd: true)]
public string $address;
public function __construct(string $name, string $phone, string $email, string $address)
{
$this->name = $name;
$this->phone = $phone;
$this->email = $email;
$this->address = $address;
}
}
// 使用
$user = new User('张三', '13812345678', 'test@example.com', '北京市朝阳区某某街道');
MaskHandler::handle($user);
print_r($user);
中间件脱敏方案(Laravel示例)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class DataMaskingMiddleware
{
protected $maskingRules = [
'users' => [
'phone' => 'mobile',
'email' => 'email',
'id_card' => 'id_card',
'name' => 'name'
],
'orders' => [
'address' => 'address',
'contact_phone' => 'mobile'
]
];
public function handle(Request $request, Closure $next)
{
$response = $next($request);
if ($response instanceof \Illuminate\Http\JsonResponse) {
$data = json_decode($response->getContent(), true);
if ($this->shouldMask($request)) {
$maskedData = $this->applyMasking($data);
$response->setData($maskedData);
}
}
return $response;
}
protected function shouldMask(Request $request): bool
{
// 如果不是浏览器请求或者需要脱敏的环境
if ($request->is('api/*')) {
return true;
}
return false;
}
protected function applyMasking(array $data): array
{
foreach ($data as $key => $value) {
if (is_array($value)) {
$data[$key] = $this->applyMasking($value);
} else {
// 检查是否有对应的脱敏规则
$model = $this->getModelName($data);
if (isset($this->maskingRules[$model][$key])) {
$data[$key] = DataMasking::mask($value, $this->maskingRules[$model][$key]);
}
}
}
return $data;
}
protected function getModelName(array $data): string
{
// 根据数据特征判断模型名称
if (isset($data['user_id'])) {
return 'users';
}
if (isset($data['order_id'])) {
return 'orders';
}
return 'default';
}
}
数据库查询时脱敏(SQL方案)
<?php
class SqlMaskingBuilder
{
/**
* 生成SQL脱敏表达式
*/
public static function buildMaskExpression(string $column, string $type): string
{
switch ($type) {
case 'mobile':
return "CONCAT(LEFT($column, 3), '****', RIGHT($column, 4))";
case 'id_card':
return "CONCAT(LEFT($column, 4), '**********', RIGHT($column, 4))";
case 'name':
return "CONCAT(LEFT($column, 1), REPEAT('*', CHAR_LENGTH($column) - 1))";
case 'email':
return "CONCAT(LEFT($column, 3), '***', SUBSTRING_INDEX($column, '@', -1))";
default:
return $column;
}
}
/**
* 生成脱敏SELECT语句
*/
public static function maskedSelect(string $table, array $columns, array $maskingRules): string
{
$selectColumns = [];
foreach ($columns as $column) {
$selectColumns[] = self::buildMaskExpression($column, $maskingRules[$column] ?? 'default');
}
return "SELECT " . implode(', ', $selectColumns) . " FROM $table";
}
}
// 使用示例
$sql = SqlMaskingBuilder::maskedSelect(
'users',
['id', 'name', 'phone', 'email'],
['name' => 'name', 'phone' => 'mobile', 'email' => 'email']
);
自定义脱敏配置(配置文件方式)
<?php
// config/masking.php
return [
'users' => [
'name' => [
'type' => 'name',
'enabled' => true
],
'phone' => [
'type' => 'mobile',
'enabled' => true
],
'email' => [
'type' => 'email',
'enabled' => true,
'case_sensitive' => false
],
'address' => [
'type' => 'custom',
'start' => 6,
'maskChar' => '*',
'keepEnd' => false
]
],
'settings' => [
'default_mask_char' => '*',
'mask_percentage' => 70
]
];
class MaskingService
{
private $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function maskField(string $model, string $field, $value)
{
if (!isset($this->config[$model][$field])) {
return $value;
}
$rule = $this->config[$model][$field];
if (isset($rule['enabled']) && !$rule['enabled']) {
return $value;
}
if ($rule['type'] === 'custom') {
return $this->customMask($value, $rule);
}
return DataMasking::mask($value, $rule['type']);
}
private function customMask($value, array $rule): string
{
$start = $rule['start'] ?? 0;
$maskChar = $rule['maskChar'] ?? '*';
$keepEnd = $rule['keepEnd'] ?? true;
// 自定义脱敏逻辑
$str = (string)$value;
$len = strlen($str);
if ($start >= $len) {
return $str;
}
// 计算脱敏长度
$maskLen = ceil($len * ($this->config['settings']['mask_percentage'] ?? 70) / 100);
$maskLen = min($maskLen, $len - $start);
$mask = str_repeat($maskChar, $maskLen);
if ($keepEnd) {
return substr($str, 0, $start) . $mask . substr($str, $start + $maskLen);
}
return substr($str, 0, $start) . $mask;
}
}
// 使用示例
$maskingService = new MaskingService(require 'config/masking.php');
echo $maskingService->maskField('users', 'name', '张三丰');
完整的脱敏工具类(包含所有常用类型)
<?php
class ComprehensiveMasker
{
// 定义脱敏类型常量
const TYPE_MOBILE = 'mobile';
const TYPE_PHONE = 'phone';
const TYPE_EMAIL = 'email';
const TYPE_ID_CARD = 'id_card';
const TYPE_BANK_CARD = 'bank_card';
const TYPE_NAME = 'name';
const TYPE_ADDRESS = 'address';
const TYPE_IP = 'ip';
const TYPE_PASSWORD = 'password';
const TYPE_TAX_NO = 'tax_no';
const TYPE_LICENSE = 'license';
// 掩码字符映射
private static $maskChar = '*';
/**
* 统一脱敏入口
*/
public static function mask($value, string $type): string
{
if ($value === null || $value === '') {
return $value ?? '';
}
switch ($type) {
case self::TYPE_MOBILE:
return self::maskMobile($value);
case self::TYPE_PHONE:
return self::maskPhone($value);
case self::TYPE_EMAIL:
return self::maskEmail($value);
case self::TYPE_ID_CARD:
return self::maskIdCard($value);
case self::TYPE_BANK_CARD:
return self::maskBankCard($value);
case self::TYPE_NAME:
return self::maskName($value);
case self::TYPE_ADDRESS:
return self::maskAddress($value);
case self::TYPE_IP:
return self::maskIp($value);
case self::TYPE_PASSWORD:
return self::maskPassword($value);
case self::TYPE_TAX_NO:
return self::maskTaxNo($value);
case self::TYPE_LICENSE:
return self::maskLicense($value);
default:
return self::maskDefault($value);
}
}
private static function maskMobile($mobile): string
{
return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '$1****$2', $mobile);
}
private static function maskPhone($phone): string
{
// 支持座机号码脱敏
return preg_replace('/^(\d{3,4})(\d{4})(\d{4})$/', '$1****$3', $phone);
}
private static function maskEmail($email): string
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
return $email;
}
list($username, $domain) = explode('@', $email);
$maskedUsername = substr($username, 0, 3) . str_repeat(self::$maskChar, strlen($username) - 3);
return $maskedUsername . '@' . $domain;
}
private static function maskIdCard($idCard): string
{
if (strlen($idCard) === 18) {
return substr($idCard, 0, 4) . str_repeat(self::$maskChar, 10) . substr($idCard, 14);
}
if (strlen($idCard) === 15) {
return substr($idCard, 0, 4) . str_repeat(self::$maskChar, 7) . substr($idCard, 11);
}
return $idCard;
}
private static function maskBankCard($cardNo): string
{
return substr($cardNo, 0, 4) . str_repeat(self::$maskChar, 8) . substr($cardNo, -4);
}
private static function maskName($name): string
{
if (mb_strlen($name) <= 1) {
return $name;
}
// 不同类型姓名处理
if (mb_strlen($name) === 2) {
// 两个字:保留姓氏
return mb_substr($name, 0, 1) . self::$maskChar;
}
return mb_substr($name, 0, 1) . str_repeat(self::$maskChar, mb_strlen($name) - 2) . mb_substr($name, -1);
}
private static function maskAddress($address): string
{
$length = mb_strlen($address);
if ($length <= 4) {
return $address;
}
// 保留前4个字符,脱敏中间部分,保留后2个字符
$maskCount = $length - 6;
return mb_substr($address, 0, 4) .
str_repeat(self::$maskChar, max(0, $maskCount)) .
mb_substr($address, -2);
}
private static function maskIp($ip): string
{
$parts = explode('.', $ip);
if (count($parts) != 4) {
return $ip;
}
return $parts[0] . '.' . self::$maskChar . '.' . self::$maskChar . '.' . $parts[3];
}
private static function maskPassword($password): string
{
return str_repeat(self::$maskChar, strlen($password));
}
private static function maskTaxNo($taxNo): string
{
if (strlen($taxNo) === 15 || strlen($taxNo) === 18) {
return substr($taxNo, 0, 2) . str_repeat(self::$maskChar, strlen($taxNo) - 6) . substr($taxNo, -4);
}
return $taxNo;
}
private static function maskLicense($license): string
{
// 营业执照号脱敏
if (strlen($license) === 18) {
return '913***********' . substr($license, -4);
}
return $license;
}
private static function maskDefault($value): string
{
if (strlen($value) <= 3) {
return self::$maskChar;
}
return substr($value, 0, 3) . str_repeat(self::$maskChar, strlen($value) - 3);
}
}
// 批量脱敏函数
function maskArray(array $data, array $rules): array
{
foreach ($rules as $field => $type) {
if (isset($data[$field])) {
$data[$field] = ComprehensiveMasker::mask($data[$field], $type);
}
}
return $data;
}
// 使用示例
$userData = [
'name' => '张三丰',
'phone' => '13812345678',
'email' => 'test@example.com',
'id_card' => '110101199001011234',
'address' => '北京市朝阳区某某街道123号',
'ip' => '192.168.1.1'
];
$rules = [
'name' => ComprehensiveMasker::TYPE_NAME,
'phone' => ComprehensiveMasker::TYPE_MOBILE,
'email' => ComprehensiveMasker::TYPE_EMAIL,
'id_card' => ComprehensiveMasker::TYPE_ID_CARD,
'address' => ComprehensiveMasker::TYPE_ADDRESS,
'ip' => ComprehensiveMasker::TYPE_IP
];
$maskedData = maskArray($userData, $rules);
print_r($maskedData);
总结与建议
脱敏方案选择建议:
- 性能要求高:使用SQL层面的脱敏
- 可维护性要求高:使用注解或配置驱动的方式
- 安全要求高:在后端强制脱敏,前端不接收敏感数据
- 数据量大:采用分批脱敏,避免一次性处理
最佳实践:
- ✅ 在生产环境默认启用脱敏
- ✅ 提供脱敏开关,用于测试环境
- ✅ 脱敏规则集中管理
- ✅ 敏感数据加密存储
- ✅ 日志中也进行脱敏
完整示例下载地址:
- GitHub: PHP Data Masking Solution
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播