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

将DataTransformer迁移至StateProvider后出现安全问题

问题核心

在API Platform 2.6中,通过DataTransformer处理/fridges/{id}/contents接口时,security表达式里的object指向原始Fridge实例,FRIDGE_READ权限的Voter能正常验证冰箱所有者权限。但升级到2.7改用StateProvider后,security表达式中的object变成了Provider返回的FridgeContents实例,导致Voter无法获取Fridge进行权限校验。

解决方案

方案1:让Voter兼容FridgeContents实例

修改现有FRIDGE_READ Voter,使其同时支持Fridge和FridgeContents实例,从FridgeContents中关联到对应Fridge完成验证:

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use App\Entity\Fridge;
use App\Entity\FridgeContents;
use Symfony\Component\Security\Core\User\UserInterface;

class FridgeReadVoter extends Voter
{
    protected function supports(string $attribute, mixed $subject): bool
    {
        return $attribute === 'FRIDGE_READ' && 
               ($subject instanceof Fridge || $subject instanceof FridgeContents);
    }

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();
        if (!$user instanceof UserInterface) {
            return false;
        }

        // 统一获取Fridge实例
        $fridge = $subject instanceof FridgeContents ? $subject->getFridge() : $subject;
        
        return $fridge->getOwner() === $user;
    }
}

优点:无需修改资源配置和Provider代码,权限逻辑集中在Voter中,符合单一职责原则。
前提:FridgeContents类需保留对Fridge的引用(比如添加getFridge()方法)。

方案2:在security表达式中直接获取原始Fridge实例

通过Symfony表达式语言的service()函数,调用Item Provider根据URI变量中的id获取Fridge实例,传入权限校验:

#[ApiResource(
    operations: [
        new Get(
            uriTemplate: '/fridges/{id}/contents',
            security: 'is_granted("FRIDGE_READ", service("api_platform.doctrine.orm.item_provider").getItem("App\Entity\Fridge", uri_variables["id"], null, []))',
            output: FridgeContents::class,
            provider: FridgeContentsProvider::class,
        ),
    ],
)]
class Fridge {}

注意:确保服务IDapi_platform.doctrine.orm.item_provider与你的实际Item Provider服务匹配,若使用自定义Item Provider,替换为对应服务ID即可。

优点:无需修改Voter和Provider,直接在资源配置中完成权限校验,逻辑集中。
缺点:表达式相对冗长,硬编码了实体类名和服务ID,后期维护需注意同步。

方案3:优化临时方案,统一权限校验逻辑

如果选择保留在Provider内做权限校验的方式,建议将安全逻辑封装到独立服务,避免Provider代码臃肿:

  1. 创建权限校验服务:
namespace App\Security;

use App\Entity\Fridge;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;

class FridgeAccessChecker
{
    public function __construct(private readonly Security $security) {}

    public function checkReadAccess(Fridge $fridge): void
    {
        if (!$this->security->isGranted('FRIDGE_READ', $fridge)) {
            throw new AccessDeniedException();
        }
    }
}
  1. 在StateProvider中注入并调用:
class FridgeContentsProvider implements ProviderInterface
{
    public function __construct(
        private readonly ProviderInterface $itemProvider,
        private readonly FridgeAccessChecker $accessChecker,
    ) {
    }

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): FridgeContents
    {
        /** @var Fridge $fridge */
        $fridge = $this->itemProvider->provide($operation, $uriVariables, $context);
        
        // 调用统一的权限校验
        $this->accessChecker->checkReadAccess($fridge);

        // ...transform data...

        return $fridgeContents;
    }
}

优点:将权限逻辑从Provider中剥离,保持Provider专注于数据转换,权限逻辑可复用。
缺点:权限校验从资源配置转移到了Provider,需确保所有相关操作都调用该校验服务。


内容的提问来源于stack exchange,提问作者Zed-K

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:05:22