You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

API-Platform中基于角色的结果过滤最佳实现方案

解决方案:基于角色的自动数据过滤与匿名化

一、基础实体配置

假设核心数据实体为DataEntry,先完成实体与机构的关联,并标记敏感字段:

// src/Entity/DataEntry.php
namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

/**
 * @ApiResource(
 *     normalizationContext={"groups"={"data:read"}},
 *     denormalizationContext={"groups"={"data:write"}}
 * )
 * @ORM\Entity(repositoryClass="App\Repository\DataEntryRepository")
 */
class DataEntry
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     * @Groups({"data:read"})
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Groups({"data:read"})
     */
    private $name;

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Institution")
     * @ORM\JoinColumn(nullable=false)
     */
    private $institution;

    // Getters & Setters
    public function getInstitution(): ?Institution
    {
        return $this->institution;
    }

    public function setInstitution(?Institution $institution): self
    {
        $this->institution = $institution;
        return $this;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;
        return $this;
    }
}

用户实体需关联所属机构(管理员可无关联):

// src/Entity/User.php
namespace App\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface
{
    // 其他字段...

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Institution")
     * @ORM\JoinColumn(nullable=true)
     */
    private $institution;

    public function getInstitution(): ?Institution
    {
        return $this->institution;
    }
}

二、自动过滤机构数据(非管理员用户)

通过API-Platform的查询构建监听器,在数据库查询阶段自动添加机构过滤条件,避免全量加载后再过滤:

// src/EventListener/DataEntryQueryFilterSubscriber.php
namespace App\EventListener;

use ApiPlatform\Core\Bridge\Doctrine\Orm\Event\QueryBuilderEvent;
use App\Entity\DataEntry;
use App\Entity\User;
use Symfony\Component\Security\Core\Security;

class DataEntryQueryFilterSubscriber
{
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function onQueryBuilder(QueryBuilderEvent $event)
    {
        $user = $this->security->getUser();
        if (!$user instanceof User || $user->hasRole('ROLE_ADMIN')) {
            return;
        }

        $institution = $user->getInstitution();
        if (!$institution) {
            $event->getQueryBuilder()->andWhere('1=0'); // 无机构用户返回空结果
            return;
        }

        $resourceClass = $event->getResourceClass();
        if ($resourceClass !== DataEntry::class) {
            return;
        }

        $rootAlias = $event->getQueryBuilder()->getRootAliases()[0];
        $event->getQueryBuilder()
            ->andWhere(sprintf('%s.institution = :institution', $rootAlias))
            ->setParameter('institution', $institution);
    }
}

在services.yaml注册监听器:

services:
    App\EventListener\DataEntryQueryFilterSubscriber:
        tags:
            - { name: api_platform.doctrine.orm.query_builder_listener, event: "post_build" }

三、管理员数据匿名化处理

针对管理员角色,在序列化阶段动态修改敏感字段内容,确保数据匿名化:

// src/EventListener/DataEntryAnonymizationListener.php
namespace App\EventListener;

use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\DataEntry;
use App\Entity\User;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Security;

class DataEntryAnonymizationListener implements EventSubscriberInterface
{
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public static function getSubscribedEvents()
    {
        return [
            KernelEvents::VIEW => ['anonymizeSensitiveData', EventPriorities::PRE_SERIALIZE],
        ];
    }

    public function anonymizeSensitiveData(ViewEvent $event)
    {
        $user = $this->security->getUser();
        if (!$user instanceof User || !$user->hasRole('ROLE_ADMIN')) {
            return;
        }

        $data = $event->getControllerResult();
        $anonymizeField = function(DataEntry $entry) {
            // 姓名匿名化:保留首字符,其余替换为*
            $originalName = $entry->getName();
            if ($originalName) {
                $entry->setName(substr($originalName, 0, 1) . str_repeat('*', strlen($originalName) - 1));
            }
        };

        if (is_iterable($data)) {
            foreach ($data as $entry) {
                if ($entry instanceof DataEntry) {
                    $anonymizeField($entry);
                }
            }
        } elseif ($data instanceof DataEntry) {
            $anonymizeField($data);
        }
    }
}

四、权限控制补充

在DataEntry的ApiResource注解中添加基础权限验证,作为双重保障:

/**
 * @ApiResource(
 *     normalizationContext={"groups"={"data:read"}},
 *     denormalizationContext={"groups"={"data:write"}},
 *     accessControl="is_granted('ROLE_USER')",
 *     accessControlMessage="需登录后访问"
 * )
 */

替代思路

  • 自定义Data Provider:重写API-Platform的Doctrine ORM Data Provider,在数据获取环节统一处理过滤与匿名化逻辑,适合复杂场景。
  • 序列化组动态切换:为管理员单独设置序列化组,移除敏感字段的组标记,仅返回非敏感字段,无需修改实体数据。

内容的提问来源于stack exchange,提问作者user19733098

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 23:03:23