如何让@Security注解优先级高于ParamConverter
这个场景我太熟悉了——Symfony默认的执行顺序确实会让ParamConverter先于安全注解生效,导致未授权访问不存在的资源时直接返回404,跳过了登录跳转的逻辑。下面给你几个实用的解决办法:
方法1:让ParamConverter忽略未找到的对象,手动处理404
这是最直接的方案,通过给@ParamConverter添加ignore_not_found选项,让它在找不到User时不立即抛出404,而是把$user变量设为null,这样安全注解会先执行,之后你再在Action里处理未找到的情况:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\Security\Http\Attribute\Security; #[Security("is_authenticated()")] #[ParamConverter("user", class: "App\Entity\User", options: ["ignore_not_found" => true])] public function someAction(?User $user): Response { if (!$user) { throw $this->createNotFoundException("User not found"); } // 你的业务逻辑 }
这样一来,未授权的请求会先触发@Security的检查,跳转到登录页;授权后的请求如果找不到User,才会返回404,完美符合你的预期。
方法2:调整事件优先级,让安全检查先于ParamConverter执行
如果你不想修改每个Action的ParamConverter,可以全局调整事件的执行顺序。Symfony的ParamConverter是通过ParamConverterListener处理的,它的默认优先级是100。而安全检查的SecurityListener优先级默认是0,所以我们需要把安全检查的优先级调高,让它在ParamConverter之前运行:
在services.yaml里添加配置:
services: Symfony\Component\Security\Http\EventListener\SecurityListener: arguments: - '@security.http_utils' - '@security.authentication.trust_resolver' - '@security.firewall.map' - '@security.access.decision_manager' - '@?security.authorization_checker' tags: - { name: kernel.event_listener, event: kernel.request, priority: 200 }
注意:这个方法会全局改变安全检查和ParamConverter的执行顺序,要确保你的其他业务逻辑不会因此受影响。
方法3:自定义ParamConverter,嵌入安全检查
如果上面的方法都不适合,你可以创建一个自定义的ParamConverter,在转换参数之前先执行安全检查:
// src/ParamConverter/SecuredUserParamConverter.php namespace App\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Security; class SecuredUserParamConverter implements ParamConverterInterface { public function __construct(private Security $security) {} public function apply(Request $request, ParamConverter $configuration): bool { // 先做安全检查 if (!$this->security->isGranted('IS_AUTHENTICATED_FULLY')) { throw new AccessDeniedException(); } // 这里写你原本的User查找逻辑,比如从请求参数获取邮箱,查询数据库 $email = $request->get('email'); $user = // 你的User查询代码 if ($user) { $request->attributes->set($configuration->getName(), $user); return true; } return false; } public function supports(ParamConverter $configuration): bool { return $configuration->getClass() === 'App\Entity\User'; } }
然后在services.yaml里注册这个自定义转换器,优先级设得比默认的高:
services: App\ParamConverter\SecuredUserParamConverter: tags: - { name: request.param_converter, priority: 200 }
这样每次转换User参数时,都会先执行安全检查,再做对象查找。
内容的提问来源于stack exchange,提问作者Dennis Haarbrink

