Cómo proteger cualquier servicio o método de tu aplicación

En el capítulo sobre seguridad, puedes ver cómo proteger un controlador requiriendo el servicio security.context desde el Contenedor de servicios y comprobando el rol del usuario actual:

// ...
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

public function helloAction($name)
{
    if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
        throw new AccessDeniedException();
    }

    // ...
}

También puedes proteger cualquier servicio de manera similar, inyectándole el servicio security.context. Para una introducción general a la inyección de dependencias en servicios consulta el capítulo Contenedor de servicios del libro. Por ejemplo, supongamos que tienes una clase NewsletterManager que envía mensajes de correo electrónico y deseas restringir su uso a únicamente los usuarios que tienen algún rol ROLE_NEWSLETTER_ADMIN. Antes de agregar la protección, la clase se ve algo parecida a esta:

// src/Acme/HelloBundle/Newsletter/NewsletterManager.php
namespace Acme\HelloBundle\Newsletter;

class NewsletterManager
{

    public function sendNewsletter()
    {
        // ... donde realmente haces el trabajo
    }

    // ...
}

Nuestro objetivo es comprobar el rol del usuario cuando se llama al método sendNewsletter(). El primer paso para esto es inyectar el servicio security.context en el objeto. Dado que no tiene sentido no realizar la comprobación de seguridad, este es un candidato ideal para el constructor de inyección, lo cual garantiza que el objeto del contexto de seguridad estará disponible dentro de la clase NewsletterManager:

namespace Acme\HelloBundle\Newsletter;

use Symfony\Component\Security\Core\SecurityContextInterface;

class NewsletterManager
{
    protected $securityContext;

    public function __construct(SecurityContextInterface $securityContext)
    {
        $this->securityContext = $securityContext;
    }

    // ...
}

Luego, en tu configuración del servicio, puedes inyectar el servicio:

  • YAML
    # src/Acme/HelloBundle/Resources/config/services.yml
    parameters:
        newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
    
    services:
        newsletter_manager:
            class:     %newsletter_manager.class%
            arguments: ["@security.context"]
  • XML
    <!-- src/Acme/HelloBundle/Resources/config/services.xml -->
    <parameters>
        <parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</parameter>
    </parameters>
    
        <services>
        <service id="newsletter_manager" class="%newsletter_manager.class%">
            <argument type="service" id="security.context"/>
        </service>
        </services>
    
  • PHP
    // src/Acme/HelloBundle/Resources/config/services.php
    use Symfony\Component\DependencyInjection\Definition;
    use Symfony\Component\DependencyInjection\Reference;
    
    $container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager');
    
    $container->setDefinition('newsletter_manager', new Definition(
        '%newsletter_manager.class%',
        array(new Reference('security.context'))
    ));
    

El servicio inyectado se puede utilizar para realizar la comprobación de seguridad cuando se llama al método sendNewsletter():

namespace Acme\HelloBundle\Newsletter;

use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\SecurityContextInterface;
// ...

class NewsletterManager
{
    protected $securityContext;

    public function __construct(SecurityContextInterface $securityContext)
    {
        $this->securityContext = $securityContext;
    }

    public function sendNewsletter()
    {
        if (false === $this->securityContext->isGranted('ROLE_NEWSLETTER_ADMIN')) {
            throw new AccessDeniedException();
        }

        // ...
    }

    // ...
}

Si el usuario actual no tiene el rol ROLE_NEWSLETTER_ADMIN, se le pedirá que inicie sesión.

Protegiendo métodos usando anotaciones

También puedes proteger con anotaciones las llamadas a métodos en cualquier servicio usando el paquete opcional JMSSecurityExtraBundle. Este paquete está incluido en la edición estándar de Symfony2.

Para habilitar la funcionalidad de las anotaciones, etiqueta el servicio que deseas proteger con la etiqueta security.secure_service (también puedes habilitar esta funcionalidad automáticamente para todos los servicios, consulta la barra lateral más adelante):

  • YAML
    # src/Acme/HelloBundle/Resources/config/services.yml
    # ...
    
    services:
        newsletter_manager:
            # ...
            tags:
                -  { name: security.secure_service }
    
  • XML
    <!-- src/Acme/HelloBundle/Resources/config/services.xml -->
    <!-- ... -->
    
        <services>
        <service id="newsletter_manager" class="%newsletter_manager.class%">
            <!-- ... -->
            <tag name="security.secure_service" />
        </service>
        </services>
    
  • PHP
    // src/Acme/HelloBundle/Resources/config/services.php
    use Symfony\Component\DependencyInjection\Definition;
    use Symfony\Component\DependencyInjection\Reference;
    
    $definition = new Definition(
        '%newsletter_manager.class%',
        array(new Reference('security.context'))
    ));
    $definition->addTag('security.secure_service');
    $container->setDefinition('newsletter_manager', $definition);
    

Entonces puedes obtener los mismos resultados que el anterior usando una anotación:

namespace Acme\HelloBundle\Newsletter;

use JMS\SecurityExtraBundle\Annotation\Secure;
// ...

class NewsletterManager
{

    /**
     * @Secure(roles="ROLE_NEWSLETTER_ADMIN")
     */
    public function sendNewsletter()
    {
        // ...
    }

    // ...
}

Nota

Las anotaciones trabajan debido a que se crea una clase delegada para la clase que realiza las comprobaciones de seguridad. Esto significa que, si bien puedes utilizar las anotaciones sobre métodos públicos y protegidos, no las puedes utilizar con los métodos privados o los métodos marcados como finales.

El JMSSecurityExtraBundle también te permite proteger los parámetros y valores devueltos de los métodos. Para más información, consulta la documentación de JMSSecurityExtraBundle.

Bifúrcame en GitHub