本文目录导读:

- 使用字符串函数直接脱敏(最常用)
- 基于用户权限的动态选择性披露
- 使用模型层的访问器(Laravel 风格)
- 使用中间件进行全局过滤(API 场景)
- 数据库级别的视图(只读场景)
- 处理 JSON 数据中的选择性披露
- 最佳实践建议
在 PHP 中实现“选择性披露”通常意味着:只向特定用户(或特定权限等级)显示部分敏感数据,而对其他用户隐藏或脱敏这些数据。
常见的应用场景包括:
- 用户手机号:显示
138****1234 - 用户邮箱:显示
a***@example.com - 银行卡号:显示
尾号 1234 - 系统密码:显示
- 用户真实姓名:只显示姓氏,如
张*
以下是几种常见的实现方案,按复杂度从低到高排列:
使用字符串函数直接脱敏(最常用)
这是最直接、性能最好的方式,适用于固定格式的数据。
function maskPhone($phone) {
return substr($phone, 0, 3) . '****' . substr($phone, -4);
}
function maskEmail($email) {
$parts = explode('@', $email);
$name = $parts[0];
$domain = $parts[1];
// 隐藏用户名的一部分
$maskedName = substr($name, 0, 2) . str_repeat('*', max(0, strlen($name) - 4)) . substr($name, -1);
return $maskedName . '@' . $domain;
}
function maskName($name) {
if (mb_strlen($name) <= 1) {
return $name;
}
// 保留第一个字符,其余隐藏
return mb_substr($name, 0, 1) . str_repeat('*', mb_strlen($name) - 1);
}
// 使用示例
echo maskPhone('13812345678'); // 138****5678
echo maskEmail('example@test.com'); // ex****e@test.com
echo maskName('张三'); // 张*
基于用户权限的动态选择性披露
如果希望根据用户的角色(如普通用户、管理员、客服)来决定是否真正显示原数据:
class SelectiveDisclosure {
public static function maskSensitiveData($data, $type, $userRole = 'user') {
// 管理员/客服可以看到完整数据
if (in_array($userRole, ['admin', 'support'])) {
return $data;
}
switch ($type) {
case 'phone':
return substr($data, 0, 3) . '****' . substr($data, -4);
case 'email':
$parts = explode('@', $data);
$name = $parts[0];
$maskedName = substr($name, 0, 2) . '***';
return $maskedName . '@' . $parts[1];
case 'id_card':
return substr($data, 0, 3) . '********' . substr($data, -4);
default:
return '***';
}
}
}
// 使用
$userRole = getUserRole(); // 'admin' 或 'user'
echo SelectiveDisclosure::maskSensitiveData('13812345678', 'phone', $userRole);
使用模型层的访问器(Laravel 风格)
如果你使用 MVC 框架(如 Laravel),可以在模型层定义访问器,根据上下文自动处理。
// User.php
class User extends Model {
// 获取手机号时自动脱敏
public function getPhoneAttribute($value) {
// 检查当前用户是否允许查看原数据
if (auth()->user() && auth()->user()->can('view_sensitive_phone')) {
return $value;
}
return substr($value, 0, 3) . '****' . substr($value, -4);
}
// 获取邮箱时自动脱敏
public function getEmailAttribute($value) {
if (auth()->user() && auth()->user()->can('view_sensitive_email')) {
return $value;
}
$parts = explode('@', $value);
return substr($parts[0], 0, 2) . '***@' . $parts[1];
}
}
使用中间件进行全局过滤(API 场景)
对于 RESTful API,可以编写中间件在响应输出前替换敏感字段:
// SelectiveDisclosureMiddleware.php
class SelectiveDisclosureMiddleware {
public function handle($request, Closure $next) {
$response = $next($request);
// 只在响应中处理
if (method_exists($response, 'getData')) {
$data = $response->getData(true);
$data = $this->maskSensitiveFields($data, $request->user());
$response->setData($data);
}
return $response;
}
private function maskSensitiveFields($data, $user) {
// 深度遍历数组/对象,对特定字段进行脱敏
array_walk_recursive($data, function(&$value, $key) use ($user) {
if (in_array($key, ['phone', 'email', 'id_card'])) {
if (!$user || !$user->can('view_' . $key)) {
$value = maskField($key, $value);
}
}
});
return $data;
}
}
数据库级别的视图(只读场景)
如果希望直接在数据库层面做选择性披露,可以创建数据库视图:
-- 普通用户视图
CREATE VIEW user_safe_view AS
SELECT
id,
CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) as phone,
CONCAT(LEFT(email, 2), '***@', SUBSTRING_INDEX(email, '@', -1)) as email,
name
FROM users;
然后在 PHP 中根据用户角色决定查询哪个表或视图。
处理 JSON 数据中的选择性披露
如果数据以 JSON 存储(如 API 响应中的嵌套数组):
function maskJsonSensitiveData(array $data, string $userRole): array {
foreach ($data as $key => &$value) {
if ($key === 'phone' && is_string($value)) {
// 普通用户脱敏,管理员保留
$value = ($userRole === 'admin') ? $value : substr($value, 0, 3) . '****' . substr($value, -4);
} elseif (is_array($value)) {
$value = maskJsonSensitiveData($value, $userRole); // 递归处理嵌套
}
}
return $data;
}
最佳实践建议
| 场景 | 推荐方案 |
|---|---|
| 简单的字段脱敏(固定格式) | 字符串函数直接处理 |
| 需要根据用户权限动态披露 | 模型访问器 + 权限检查 |
| API 接口统一处理 | 中间件过滤 |
| 多语言/多字符集(中文字符) | 使用 mb_substr() 处理 |
| 性能敏感 | 在 SQL 查询中使用 CONCAT 直接脱敏 |
| 需要审计日志 | 在数据输出前记录原始值和脱敏后的值 |
安全提示:
- 永远不要在前端(JavaScript)做关键数据的选择性披露,因为前端代码可以被篡改。
- 所有敏感数据的脱敏操作必须在服务端(PHP)完成。
- 如果你需要“选择性地向不同用户展示不同数据”,建议配合一个权限系统来处理。