在Symfony 4中的控制器内部覆盖@Security批注

时间:2018-07-22 16:56:21

标签: symfony annotations

今天,我开始将我的应用程序从symfony 3升级到4(以及相关的库),但我不明白为什么我无法使某些路由正常工作(我遇到了401错误,但它们应该是公共路由)因此在那里没有进行安全检查),然后我最终发现了这个问题:@Security annotation on controller class being overridden by action method

最近对该问题的评论说,在先前版本的symfony框架额外捆绑中,如果将安全注释同时放在一个类和该类中的方法上,则该方法注释将覆盖该类注释,现在它们堆叠代替。

这也可以在版本4.0的SensioFramework更改日志https://github.com/sensiolabs/SensioFrameworkExtraBundle/blob/master/CHANGELOG.md上看到(虽然不是很清楚,因为您已经在类和方法上都添加了@Security注释)

  

允许使用多个@Security批注(类和方法)

对我来说,这是一个很大的变化,因为我的应用程序中的许多路由都依赖于该行为(类似于Symfony 1,您可以在其中设置默认的安全行为,然后为每个操作指定一个更具体的行为)

/**
 * @Route("my-route")
 * @Security("is_granted('IS_AUTHENTICATED_FULLY')")
 */
class MyController extends Controller {

    /**
     * In Symfony 3.x this would've removed security checks for the route,
     * now it checks both the class and the method Security expressions 
     * @Security(true)
     */
    public function myAction(Request $request) {

    }
}

除了“不升级到symfony 4”或“重新组织代码”(这是我的“计划B”)以外,还有其他方法可以使此行为恢复吗?诸如配置选项之类的东西。 我似乎找不到任何有关此的信息

2 个答案:

答案 0 :(得分:1)

我忘记了这个问题,但是确实通过制作自己的注释和EventListener来解决了这个问题。 免责声明:

1)我的代码使用Dependency Injection捆绑软件通过注释注入和声明服务
2)我按原样共享代码,没有任何保证,它也对您有用,但我希望您能从中得到要点

我创建了2个注释(@IsGrantedDefault和@SecurityDefault),它们的工作方式与@IsGranted和@Security(它们实际上是对原始注释的扩展)完全一样,只是它们只能应用于类,然后我创建了2个事件侦听器,每个事件侦听器一个注解。事件侦听器还扩展了原始事件侦听器,但它们只是检查某个方法是否已具有Security或IsGranted批注,在这种情况下,它们将不执行任何操作。

IsGrantedDefault.php

<?php

/*
 * @author valepu
 */

namespace App\Project\AppBundle\Annotation;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;

/**
 * @Annotation
 * @Target("CLASS")
 */
class IsGrantedDefault extends IsGranted {
    public function getAliasName() {
        return 'is_granted_default';
    }

    public function allowArray() {
        return false;
    }
}

SecurityDefault.php

<?php

/*
 * @author valepu
 */

namespace App\Project\AppBundle\Annotation;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;

/**
 * @Annotation
 * @Target("CLASS")
 */
class SecurityDefault extends Security {
    public function getAliasName() {
        return 'security_default';
    }

    public function allowArray() {
        return false;
    }
}

DefaultListenerTrait.php(值:: DEFAULT_LISTENER_PREFIX只是带有下划线“ _”的字符串)

<?php

/*
 * @author valepu
 */
namespace App\Project\AppBundle\Event\Traits;

use App\Project\AppBundle\Utils\Values;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;


Trait DefaultListenerTrait {

    /**
     * @var string
     */
    private $defaultAttribute;

    /**
     * @var string
     */
    private $otherAttributes = [];

    /**
     * @var string
     */
    private $attribute;

    /**
     * Sets the class attributes
     * @param [type] $defaultAnnotation
     * @param string|null $modifyAttr
     * @return void
     */
    protected function setAttributes($defaultAnnotation, ?string $modifyAttr) {
        //Get the attirbutes names
        $this->attribute = $modifyAttr;
        $this->defaultAttribute = Values::DEFAULT_LISTENER_PREFIX . $defaultAnnotation->getAliasName();

        $annotations = [new IsGranted([]), new Security([])];
        foreach($annotations as $annotation) {
            $this->otherAttributes[] = Values::DEFAULT_LISTENER_PREFIX . $annotation->getAliasName();
        }
    }

    /**
     * Checks wheter or not the request needs to be handled by the annotation. If it does adds the correct attribute to the request
     * @param \Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent $event
     * @return boolean
     */
    protected function updateDefaultListener(FilterControllerArgumentsEvent $event) {
        $request = $event->getRequest();
        $default = $request->attributes->get($this->defaultAttribute);

        //If there's already an "IsGranted" annotation or there's no "IsGrantedDefault" annotation
        if (!$default) {
            return false;
        }

        foreach($this->otherAttributes as $attr) {
            if ($request->attributes->get($attr) || !$default) {
                return false;
            }
        }

        //We set IsGranted from the default and then call the parent eventListener so that it can handle the security
        $request->attributes->set($this->attribute, [$default]);
        return true;
    }

    /**
     * Calls the event listener for the class if the request is handled by the class
     * @param \Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent $event
     * @return void
     */
    protected function callEventListener(FilterControllerArgumentsEvent $event) {
        if($this->updateDefaultListener($event)) {
            parent::onKernelControllerArguments($event);
        }
    }
}

IsGrantedDefaultListener.php

<?php

/*
 * @author valepu
 */

namespace App\Project\AppBundle\Event;

use App\Project\AppBundle\Annotation\IsGrantedDefault;
use App\Project\AppBundle\Event\Traits\DefaultListenerTrait;
use App\Project\AppBundle\Utils\Values;
use RS\DiExtraBundle\Annotation as DI;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\IsGrantedListener;
use Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

/**
 * @DI\Service(autowire = true)
 * @DI\Tag("kernel.event_subscriber")
 */
class IsGrantedDefaultListener extends IsGrantedListener {
    use DefaultListenerTrait;

    /**
     * @param \Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter $argumentNameConverter
     * @param \Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface $authChecker
     * @DI\InjectParams({
     *  "argumentNameConverter" = @DI\Inject("framework_extra_bundle.argument_name_convertor"),
     *  "authChecker" = @DI\Inject("security.authorization_checker")
     * })
     */
    public function __construct(ArgumentNameConverter $argumentNameConverter, AuthorizationCheckerInterface $authChecker = null) {
        parent::__construct($argumentNameConverter, $authChecker);
        $modifyAttr = new IsGranted([]);
        $this->setAttributes(new IsGrantedDefault([]), Values::DEFAULT_LISTENER_PREFIX . $modifyAttr->getAliasName());
    }

    /**
     * @param \Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent $event
     * @return void
     */
    public function onKernelControllerArguments(FilterControllerArgumentsEvent $event) {
        $this->callEventListener($event);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents() {
        return [KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments'];
    }
}

SecurityDefaultListener.php

<?php

/*
 * @author valepu
 */

namespace App\Project\AppBundle\Event;

use App\Project\AppBundle\Annotation\SecurityDefault;
use App\Project\AppBundle\Event\Traits\DefaultListenerTrait;
use App\Project\AppBundle\Utils\Values;
use Psr\Log\LoggerInterface;
use RS\DiExtraBundle\Annotation as DI;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Sensio\Bundle\FrameworkExtraBundle\EventListener\SecurityListener;
use Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter;
use Sensio\Bundle\FrameworkExtraBundle\Security\ExpressionLanguage;
use Symfony\Component\HttpKernel\Event\FilterControllerArgumentsEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;

/**
 * @DI\Service(autowire = true)
 * @DI\Tag("kernel.event_subscriber")
 */
class SecurityDefaultListener extends SecurityListener {
    use DefaultListenerTrait;

    /**
     * @param \Sensio\Bundle\FrameworkExtraBundle\Request\ArgumentNameConverter $argumentNameConverter
     * @param \Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface $authChecker
     * @DI\InjectParams({
     *  "argumentNameConverter" = @DI\Inject("framework_extra_bundle.argument_name_convertor"),
     *  "language" = @DI\Inject("sensio_framework_extra.security.expression_language.default"),
     *  "trustResolver" = @DI\Inject("security.authentication.trust_resolver"),
     *  "roleHierarchy" = @DI\Inject("security.role_hierarchy"),
     *  "tokenStorage" = @DI\Inject("security.token_storage"),
     *  "authChecker" = @DI\Inject("security.authorization_checker"),
     *  "logger" = @DI\Inject("logger")
     * })
     *
     */
    public function __construct(ArgumentNameConverter $argumentNameConverter, ExpressionLanguage $language = null, AuthenticationTrustResolverInterface $trustResolver = null, RoleHierarchyInterface $roleHierarchy = null, TokenStorageInterface $tokenStorage = null, AuthorizationCheckerInterface $authChecker = null, LoggerInterface $logger = null) {
        parent::__construct($argumentNameConverter, $language, $trustResolver, $roleHierarchy, $tokenStorage, $authChecker, $logger);
        $modifyAttr = new Security([]);
        $this->setAttributes(new SecurityDefault([]), Values::DEFAULT_LISTENER_PREFIX . $modifyAttr->getAliasName());
    }

    public function onKernelControllerArguments(FilterControllerArgumentsEvent $event) {
        $this->callEventListener($event);
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents() {
        return [KernelEvents::CONTROLLER_ARGUMENTS => 'onKernelControllerArguments'];
    }
}

答案 1 :(得分:-1)

您可以删除类批注并在所有方法上声明它们