La seguridad es un proceso de dos etapas, cuyo objetivo es evitar que un usuario acceda a un recurso al cual no debería tener acceso.
En el primer paso del proceso, el sistema de seguridad identifica quién es el usuario obligándolo a presentar algún tipo de identificación. Esto se llama autenticación, y significa que el sistema está tratando de determinar quién eres.
Una vez que el sistema sabe quien eres, el siguiente paso es determinar si deberías tener acceso a un determinado recurso. Esta parte del proceso se llama autorización, y significa que el sistema está comprobando si tienes suficientes privilegios para realizar una determinada acción.
Puesto que la mejor manera de aprender es viendo un ejemplo, empieza asegurando tu aplicación con autenticación HTTP básica.
Nota
El componente Security de Symfony está disponible como una biblioteca PHP independiente para usarla en cualquier proyecto PHP.
Puedes ajustar el componente de seguridad a través de la configuración de tu aplicación. De hecho, la mayoría de las opciones de seguridad estándar son sólo cuestión de usar los ajustes correctos. La siguiente configuración le dice a Symfony que proteja cualquier URL coincidente con /admin/* y pida al usuario sus credenciales mediante autenticación HTTP básica (es decir, el cuadro de dialogo a la vieja escuela: nombre de usuario/contraseña):
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
http_basic:
realm: "Secured Demo Area"
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
providers:
in_memory:
memory:
users:
ryan: { password: ryanpass, roles: 'ROLE_USER' }
admin: { password: kitten, roles: 'ROLE_ADMIN' }
encoders:
Symfony\Component\Security\Core\User\User: plaintext
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<!-- app/config/security.xml -->
<config>
<firewall name="secured_area" pattern="^/">
<anonymous />
<http-basic realm="Secured Demo Area" />
</firewall>
<access-control>
<rule path="^/admin" role="ROLE_ADMIN" />
</access-control>
<provider name="in_memory">
<memory>
<user name="ryan" password="ryanpass" roles="ROLE_USER" />
<user name="admin" password="kitten" roles="ROLE_ADMIN" />
</memory>
</provider>
<encoder class="Symfony\Component\Security\Core\User\User" algorithm="plaintext" />
</config>
</srv:container>
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
'pattern' => '^/',
'anonymous' => array(),
'http_basic' => array(
'realm' => 'Secured Demo Area',
),
),
),
'access_control' => array(
array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
),
'providers' => array(
'in_memory' => array(
'memory' => array(
'users' => array(
'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'),
'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'),
),
),
),
),
'encoders' => array(
'Symfony\Component\Security\Core\User\User' => 'plaintext',
),
));
Truco
Una distribución estándar de Symfony separa la configuración de seguridad en un archivo independiente (por ejemplo, app/config/security.yml). Si no tienes un archivo de seguridad autónomo, puedes poner la configuración directamente en el archivo de configuración principal (por ejemplo, app/config/config.yml).
El resultado final de esta configuración es un sistema de seguridad totalmente operativo que tiene el siguiente aspecto:
Veamos brevemente cómo funciona la seguridad y cómo entra en juego cada parte de la configuración.
El sistema de seguridad de Symfony trabaja identificando a un usuario (es decir, la autenticación) y comprobando si ese usuario debe tener acceso a una URL o recurso específico.
Cuando un usuario hace una petición a una URL que está protegida por un cortafuegos, se activa el sistema de seguridad. El trabajo del cortafuegos es determinar si el usuario necesita estar autenticado, y si lo hace, enviar una respuesta al usuario para iniciar el proceso de autenticación.
Un cortafuegos se activa cuando la URL de una petición entrante concuerda con el patrón de la expresión regular configurada en el valor config del cortafuegos. En este ejemplo el patrón (^/) concordará con cada petición entrante. El hecho de que el cortafuegos esté activado no significa, sin embargo, que el nombre de usuario de autenticación HTTP y el cuadro de diálogo de la contraseña se muestre en cada URL. Por ejemplo, cualquier usuario puede acceder a /foo sin que se le pida se autentique.
Esto funciona en primer lugar porque el cortafuegos permite usuarios anónimos a través del parámetro de configuración anonymous. En otras palabras, el cortafuegos no requiere que el usuario se autentique plenamente de inmediato. Y puesto que no hay rol especial necesario para acceder a /foo (bajo la sección access_control), la petición se puede llevar a cabo sin solicitar al usuario se autentique.
Si eliminas la clave anonymous, el cortafuegos siempre hará que un usuario se autentique inmediatamente.
Si un usuario solicita /admin/foo, sin embargo, el proceso se comporta de manera diferente. Esto se debe a la sección de configuración access_control la cual dice que cualquier URL coincidente con el patrón de la expresión regular ^/admin (es decir, /admin o cualquier cosa coincidente con /admin/*) requiere el rol ROLE_ADMIN. Los roles son la base para la mayor parte de la autorización: el usuario puede acceder a /admin/foo sólo si cuenta con el rol ROLE_ADMIN.
Como antes, cuando el usuario hace la petición originalmente, el cortafuegos no solicita ningún tipo de identificación. Sin embargo, tan pronto como la capa de control de acceso niega el acceso a los usuarios (ya que el usuario anónimo no tiene el rol ROLE_ADMIN), el servidor de seguridad entra en acción e inicia el proceso de autenticación). El proceso de autenticación depende del mecanismo de autenticación que utilices. Por ejemplo, si estás utilizando el método de autenticación con formulario de acceso, el usuario será redirigido a la página de inicio de sesión. Si estás utilizando autenticación HTTP, se enviará al usuario una respuesta HTTP 401 para que el usuario vea el cuadro de diálogo de nombre de usuario y contraseña.
Ahora el usuario de nuevo tiene la posibilidad de presentar sus credenciales a la aplicación. Si las credenciales son válidas, se puede intentar de nuevo la petición original.
En este ejemplo, el usuario ryan se autentica correctamente con el cortafuegos. Pero como ryan no cuenta con el rol ROLE_ADMIN, se le sigue negando el acceso a /admin/foo. En última instancia, esto significa que el usuario debe ver algún tipo de mensaje indicándole que se le ha denegado el acceso.
Truco
Cuando Symfony niega el acceso al usuario, él verá una pantalla de error y recibe un código de estado HTTP 403 (Prohibido). Puedes personalizar la pantalla de error, acceso denegado, siguiendo las instrucciones de las Páginas de error en el artículo para personalizar la página de error 403 del recetario.
Por último, si el usuario admin solicita /admin/foo, se lleva a cabo un proceso similar, excepto que ahora, después de haberse autenticado, la capa de control de acceso le permitirá pasar a través de la petición:
El flujo de la petición cuando un usuario solicita un recurso protegido es sencillo, pero increíblemente flexible. Como verás más adelante, la autenticación se puede realizar de varias maneras, incluyendo a través de un formulario de acceso, certificados X.509 o la autenticación del usuario a través de Twitter. Independientemente del método de autenticación, el flujo de la petición siempre es el mismo:
Nota
El proceso exacto realmente depende un poco en el mecanismo de autenticación utilizado. Por ejemplo, cuando utilizas el formulario de acceso, el usuario presenta sus credenciales a una URL que procesa el formulario (por ejemplo /login_check) y luego es redirigido a la dirección solicitada originalmente (por ejemplo /admin/foo). Pero con la autenticación HTTP, el usuario envía sus credenciales directamente a la URL original (por ejemplo /admin/foo) y luego la página se devuelve al usuario en la misma petición (es decir, sin redirección).
Este tipo de idiosincrasia no debería causar ningún problema, pero es bueno tenerla en cuenta.
Truco
También aprenderás más adelante cómo puedes proteger cualquier cosa en Symfony2, incluidos controladores específicos, objetos, e incluso métodos PHP.
Truco
En esta sección, aprenderás cómo crear un formulario de acceso básico que continúa usando los usuarios definidos en el código del archivo security.yml.
Para cargar usuarios desde la base de datos, por favor consulta Cómo cargar usuarios desde la base de datos con seguridad (el Proveedor de entidad). Al leer este artículo y esta sección, puedes crear un sistema de formularios de acceso completo que carga usuarios desde la base de datos.
Hasta ahora, hemos visto cómo cubrir tu aplicación bajo un cortafuegos y proteger el acceso a determinadas zonas con roles. Al usar la autenticación HTTP, puedes aprovechar sin esfuerzo, el cuadro de diálogo nativo nombre de usuario/contraseña que ofrecen todos los navegadores. Sin embargo, fuera de la caja, Symfony es compatible con múltiples mecanismos de autenticación. Para información detallada sobre todos ellos, consulta la Referencia para afinar el sistema de seguridad.
En esta sección, vamos a mejorar este proceso permitiendo la autenticación del usuario a través de un formulario de acceso HTML tradicional.
En primer lugar, activa el formulario de acceso en el cortafuegos:
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: login
check_path: login_check
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<!-- app/config/security.xml -->
<config>
<firewall name="secured_area" pattern="^/">
<anonymous />
<form-login login_path="login" check_path="login_check" />
</firewall>
</config>
</srv:container>
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
'pattern' => '^/',
'anonymous' => array(),
'form_login' => array(
'login_path' => 'login',
'check_path' => 'login_check',
),
),
),
));
Truco
Si no necesitas personalizar tus valores login_path o check_path (los valores utilizados aquí son los valores predeterminados), puedes acortar tu configuración:
form_login: ~
<form-login />
'form_login' => array(),
Ahora, cuando el sistema de seguridad inicia el proceso de autenticación, redirige al usuario al formulario de acceso (predeterminado a /login). La implementación visual de este formulario de acceso es tu trabajo. Primero, crea las dos rutas que utilizarás en la configuración de seguridad: La ruta login mostrará el formulario de inicio de sesión (es decir /login) y la ruta login_check procesará el formulario enviado (es decir /login_check):
# app/config/routing.yml
login:
pattern: /login
defaults: { _controller: AcmeSecurityBundle:Security:login }
login_check:
pattern: /login_check
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="login" pattern="/login">
<default key="_controller">AcmeSecurityBundle:Security:login</default>
</route>
<route id="login_check" pattern="/login_check" />
</routes>
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('login', new Route('/login', array(
'_controller' => 'AcmeDemoBundle:Security:login',
)));
$collection->add('login_check', new Route('/login_check', array()));
return $collection;
Nota
No necesitas implementar un controlador para la URL /login_check ya que el cortafuegos automáticamente captura y procesa cualquier formulario enviado a esta URL.
Nuevo en la versión 2.1: A partir de Symfony 2.1, debes tener configuradas las rutas para tus claves login_path, check_path y logout. Estas claves pueden ser nombres de ruta (tal como muestra este ejemplo) o las URL que tienen rutas configuradas para ello.
Ten en cuenta que el nombre de la ruta login coincide con el valor login_path configurado, a donde el sistema de seguridad redirigirá a los usuarios que necesiten ingresar.
A continuación, crea el controlador que mostrará el formulario de acceso:
// src/Acme/SecurityBundle/Controller/SecurityController.php;
namespace Acme\SecurityBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;
class SecurityController extends Controller
{
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
// obtiene el error de inicio de sesión si lo hay
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(
SecurityContext::AUTHENTICATION_ERROR
);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render(
'AcmeSecurityBundle:Security:login.html.twig',
array(
// último nombre de usuario ingresado
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
)
);
}
}
No dejes que este controlador te confunda. Como veremos en un momento, cuando el usuario envía el formulario, el sistema de seguridad automáticamente se encarga de procesar la recepción del formulario por ti. Si el usuario ha presentado un nombre de usuario o contraseña no válidos, este controlador lee el error del formulario enviado desde el sistema de seguridad de modo que se pueda mostrar al usuario.
En otras palabras, tu trabajo es mostrar el formulario al usuario y los errores de ingreso que puedan haber ocurrido, pero, el propio sistema de seguridad se encarga de verificar el nombre de usuario y contraseña y la autenticación del usuario.
Por último, crea la plantilla correspondiente:
{# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
{% if error %}
<div>{{ error.message }}</div>
{% endif %}
<form action="{{ path('login_check') }}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="{{ last_username }}" />
<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
{#
Si deseas controlar la URL a la que rediriges al
usuario en caso de éxito (más detalles abajo)
<input type="hidden" name="_target_path" value="/account" />
#}
<button type="submit">login</button>
</form>
<!-- src/Acme/SecurityBundle/Resources/views/Security/login.html.php -->
<?php if ($error): ?>
<div><?php echo $error->getMessage() ?></div>
<?php endif; ?>
<form action="<?php echo $view['router']->generate('login_check') ?>" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="<?php echo $last_username ?>" />
<label for="password">Password:</label>
<input type="password" id="password" name="_password" />
<!--
Si deseas controlar la URL a la que rediriges al usuario en caso de éxito (más detalles abajo)
<input type="hidden" name="_target_path" value="/account" />
-->
<button type="submit">login</button>
</form>
Truco
La variable error pasada a la plantilla es una instancia de Symfony\Component\Security\Core\Exception\AuthenticationException. Esta puede contener más información —o incluso información confidencial— sobre el fallo de autenticación, ¡por lo tanto utilízala prudentemente!
El formulario tiene muy pocos requisitos. En primer lugar, presentando el formulario a /login_check (a través de la ruta login_check), el sistema de seguridad debe interceptar el envío del formulario y procesarlo automáticamente. En segundo lugar, el sistema de seguridad espera que los campos presentados se llamen _username y _password (estos nombres de campo se pueden configurar).
¡Y eso es todo! Cuando envías el formulario, el sistema de seguridad automáticamente comprobará las credenciales del usuario y, o bien autenticará al usuario o lo enviará al formulario de acceso donde se puede mostrar el error.
Revisemos todo el proceso:
Por omisión, si las credenciales presentadas son correctas, el usuario será redirigido a la página solicitada originalmente (por ejemplo /admin/foo). Si originalmente el usuario fue directo a la página de inicio de sesión, será redirigido a la página principal. Esto puede ser altamente personalizado, lo cual te permite, por ejemplo, redirigir al usuario a una URL específica.
Para más detalles sobre esto y cómo personalizar el proceso de entrada en general, consulta Cómo personalizar el formulario de acceso.
El primer paso en la seguridad siempre es la autenticación: el proceso de verificar quién es el usuario. Con Symfony, la autenticación se puede hacer de cualquier manera —a través de un formulario de acceso, autenticación básica HTTP, e incluso a través de Facebook.
Una vez que el usuario se ha autenticado, comienza la autorización. La autorización proporciona una forma estándar y potente para decidir si un usuario puede acceder a algún recurso (una URL, un modelo de objetos, una llamada a un método, ...). Esto funciona asignando roles específicos a cada usuario y, a continuación, requiriendo diferentes roles para diferentes recursos.
El proceso de autorización tiene dos lados diferentes:
En esta sección, nos centraremos en cómo proteger diferentes recursos (por ejemplo, URL, llamadas a métodos, etc.) con diferentes roles. Más tarde, aprenderás más sobre cómo crear y asignar roles a los usuarios.
La forma más básica para proteger parte de tu aplicación es asegurar un patrón de URL completo. Ya lo viste en el primer ejemplo de este capítulo, cómo algo que coincide con el patrón de la expresión regular ^/admin requiere el rol ROLE_ADMIN.
Prudencia
Entender exactamente cómo trabaja access_control es muy importante para garantizar que tu aplicación está protegida correctamente. Ve más adelante Entendiendo cómo trabaja access_control para información detallada.
Puedes definir tantos patrones URL como necesites —cada uno es una expresión regular—.
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
- { path: ^/admin, roles: ROLE_ADMIN }
<!-- app/config/security.xml -->
<config>
<!-- ... -->
<rule path="^/admin/users" role="ROLE_SUPER_ADMIN" />
<rule path="^/admin" role="ROLE_ADMIN" />
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
// ...
'access_control' => array(
array('path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'),
array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
),
));
Truco
Al prefijar la ruta con ^ te aseguras que sólo coinciden las URL que comienzan con ese patrón. Por ejemplo, una ruta de simplemente /admin (sin el ^) correctamente coincidirá con /admin/foo pero también coincide con la URL /foo/admin.
Por cada petición entrante, Symfony2 comprueba cada opción access_control para encontrar una que concuerde con la petición actual. Tan pronto como encuentra una entrada access_control coincidente, se detiene —únicamente si el primer access_control concordante se usa para forzar el acceso—.
Cada access_control tiene varias opciones que configuran dos diferentes cosas: (a) la petición entrante emparejada debe tener esta entrada de control de acceso y (b) una vez emparejada, debe tener algún tipo de restricción de acceso aplicable:
(a) Emparejando Opciones
Symfony2 crea una instancia de Symfony\Component\HttpFoundation\RequestMatcher para cada entrada access_control, la cual determina si o no se debería usar un determinado control de acceso en esa petición. Las siguientes opciones de access_control se utilizan para emparejar:
Toma las siguientes entradas de access_control como ejemplo:
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }
- { path: ^/admin, roles: ROLE_USER_HOST, host: symfony.com }
- { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }
- { path: ^/admin, roles: ROLE_USER }
<access-control>
<rule path="^/admin" role="ROLE_USER_IP" ip="127.0.0.1" />
<rule path="^/admin" role="ROLE_USER_HOST" host="symfony.com" />
<rule path="^/admin" role="ROLE_USER_METHOD" method="POST, PUT" />
<rule path="^/admin" role="ROLE_USER" />
</access-control>
'access_control' => array(
array('path' => '^/admin', 'role' => 'ROLE_USER_IP', 'ip' => '127.0.0.1'),
array('path' => '^/admin', 'role' => 'ROLE_USER_HOST', 'host' => 'symfony.com'),
array('path' => '^/admin', 'role' => 'ROLE_USER_METHOD', 'method' => 'POST, PUT'),
array('path' => '^/admin', 'role' => 'ROLE_USER'),
),
Para cada petición entrante, Symfony debe decidir cuál access_control utilizar basándose en la URI, la dirección IP del cliente, el nombre del servidor entrante, y el método de la petición. Recuerda, se usa la primera regla que coincida, y si para una entrada no se especifican ip, host o method, ese access_control emparejará con cualquier ip, host o method:
URI | IP | HOST | METHOD | access_control | ¿Porqué? |
/admin/user | 127.0.0.1 | example.com | GET | regla #1 (ROLE_USER_IP) | La URI empareja con path y la IP con ip. |
/admin/user | 127.0.0.1 | symfony.com | GET | regla #1 (ROLE_USER_IP) | path e ip todavía concuerdan. Esta además debería emparejar con la entrada ROLE_USER_HOST, pero sólo si se usa el primer access_control coincidente. |
/admin/user | 168.0.0.1 | symfony.com | GET | regla #2 (ROLE_USER_HOST) | La ip no concuerda con la primera regla, por lo tanto se usa la segunda regla (la cual concuerda). |
/admin/user | 168.0.0.1 | symfony.com | POST | regla #2 (ROLE_USER_HOST) | La segunda regla todavía concuerda. Esta además debería emparejar con la tercera regla (ROLE_USER_METHOD), pero solo si se usa la primer access_control coincidente. |
/admin/user | 168.0.0.1 | example.com | POST | regla #3 (ROLE_USER_METHOD) | La ip y host no concuerdan con las primeras dos entradas, pero la tercera —ROLE_USER_METHOD— concuerda y se usa. |
/admin/user | 168.0.0.1 | example.com | GET | regla #4 (ROLE_USER) | La ip, host y method evitan que las primeras tres entradas concuerden. Pero debido a que la URI concuerda con el patrón path de la entrada ROLE_USER, esta se usa. |
/foo | 127.0.0.1 | symfony.com | POST | no hay entradas concordantes | Esta no concuerda con ninguna regla access_control, debido a que la URI no concuerda con los valores de path. |
(b) Forzando el acceso
Una vez que Symfony2 ha decidido cuál entrada access_control concuerda (si la hay), entonces aplica las restricciones de acceso basándose en las opciones roles y requires_channel:
Truco
Si el acceso es denegado, el sistema intentará autenticar al usuario si aún no lo está (p. ej. redirigiendo al usuario a la página de inicio de sesión). Si el usuario ya inició sesión, se mostrará la página del error 403 «acceso denegado». Consulta Cómo personalizar páginas de error para más información.
En algunas situaciones puede surgir la necesidad de restringir el acceso a una determinada ruta basándose en la IP. Esto es importante particularmente en el caso de la Inclusión del borde lateral (ESI), por ejemplo. Cuándo ESI está habilitada, es recomendable proteger el acceso a direcciones URL ESI. De hecho, algunas ESI pueden contener algo de contenido privado tal como la información del usuario actual. Para impedir cualquier acceso directo a estos recursos desde un navegador web (deduciendo el patrón ESI de la URL), la ruta ESI se debe asegurar para que únicamente sea visible desde la caché de un delegado inverso de confianza.
Aquí tienes un ejemplo de cómo podrías proteger todas las rutas ESI que empiezan con un determinado prefijo, /esi, para que no se accedan desde el exterior:
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/esi, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
- { path: ^/esi, roles: ROLE_NO_ACCESS }
<access-control>
<rule path="^/esi" role="IS_AUTHENTICATED_ANONYMOUSLY" ip="127.0.0.1" />
<rule path="^/esi" role="ROLE_NO_ACCESS" />
</access-control>
'access_control' => array(
array('path' => '^/esi', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'ip' => '127.0.0.1'),
array('path' => '^/esi', 'role' => 'ROLE_NO_ACCESS'),
),
Así es como trabaja cuándo la ruta es /esi/algo proveniente de la IP 10.0.0.1:
Ahora, si la misma petición proviene de 127.0.0.1:
También puedes requerir que un usuario acceda a una URL vía SSL; Sólo utiliza el argumento requires_channel en cualquier entrada del access_control:
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
<access-control>
<rule path="^/cart/checkout" role="IS_AUTHENTICATED_ANONYMOUSLY" requires_channel="https" />
</access-control>
'access_control' => array(
array('path' => '^/cart/checkout', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'requires_channel' => 'https'),
),
Proteger tu aplicación basándote en los patrones URL es fácil, pero, en algunos casos, puede no estar suficientemente bien ajustado. Cuando sea necesario, fácilmente puedes forzar la autorización desde un controlador:
// ...
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 optar por instalar y utilizar el opcional JMSSecurityExtraBundle, con el cual puedes asegurar tu controlador usando anotaciones:
// ...
use JMS\SecurityExtraBundle\Annotation\Secure;
/**
* @Secure(roles="ROLE_ADMIN")
*/
public function helloAction($name)
{
// ...
}
Para más información, consulta la documentación de JMSSecurityExtraBundle. Si estás usando la distribución estándar de Symfony, este paquete está disponible de forma predeterminada. Si no es así, lo puedes descargar e instalar.
De hecho, en Symfony puedes proteger cualquier cosa utilizando una estrategia similar a la observada en la sección anterior. Por ejemplo, supongamos que tienes un servicio (es decir, una clase PHP), cuyo trabajo consiste en enviar mensajes de correo electrónico de un usuario a otro. Puedes restringir el uso de esta clase —no importa dónde se esté utilizando— a los usuarios que tienen un rol específico.
Para más información sobre cómo utilizar el componente de seguridad para proteger diferentes servicios y métodos en tu aplicación, consulta Cómo proteger cualquier servicio o método de tu aplicación.
Imagina que estás diseñando un sistema de blog donde los usuarios pueden comentar tus entradas. Ahora, deseas que un usuario pueda editar sus propios comentarios, pero no los de otros usuarios. Además, como usuario admin, quieres tener la posibilidad de editar todos los comentarios.
El componente de seguridad viene con un sistema opcional de lista de control de acceso (ACL) que puedes utilizar cuando sea necesario para controlar el acceso a instancias individuales de un objeto en el sistema. Sin ACL, puedes proteger tu sistema para que sólo determinados usuarios puedan editar los comentarios del blog en general. Pero con ACL, puedes restringir o permitir el acceso en base a comentario por comentario.
Para más información, consulta el artículo del recetario: Cómo usar las listas para el control de acceso (ACL).
En las secciones anteriores, aprendiste cómo puedes proteger diferentes recursos que requieren un conjunto de roles para un recurso. En esta sección vamos a explorar el otro lado de la autorización: los usuarios.
Durante la autenticación, el usuario envía un conjunto de credenciales (por lo general un nombre de usuario y contraseña). El trabajo del sistema de autenticación es concordar esas credenciales contra una piscina de usuarios. Entonces, ¿de dónde viene esta lista de usuarios?
En Symfony2, los usuarios pueden venir de cualquier parte —un archivo de configuración, una tabla de base de datos, un servicio web, o cualquier otra cosa que se te ocurra. Todo lo que proporcione uno o más usuarios al sistema de autenticación se conoce como «proveedor de usuario». Symfony2 de serie viene con los dos proveedores de usuario más comunes: uno que carga los usuarios de un archivo de configuración y otro que carga usuarios de una tabla de la base de datos.
La forma más fácil para especificar usuarios es directamente en un archivo de configuración. De hecho, ya lo has visto en algunos ejemplos de este capítulo.
# app/config/security.yml
security:
# ...
providers:
default_provider:
memory:
users:
ryan: { password: ryanpass, roles: 'ROLE_USER' }
admin: { password: kitten, roles: 'ROLE_ADMIN' }
<!-- app/config/security.xml -->
<config>
<!-- ... -->
<provider name="default_provider">
<memory>
<user name="ryan" password="ryanpass" roles="ROLE_USER" />
<user name="admin" password="kitten" roles="ROLE_ADMIN" />
</memory>
</provider>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
// ...
'providers' => array(
'default_provider' => array(
'memory' => array(
'users' => array(
'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'),
'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'),
),
),
),
),
));
Este proveedor de usuario se denomina proveedor de usuario «en memoria», ya que los usuarios no se almacenan en alguna parte de una base de datos. El objeto usuario en realidad lo proporciona Symfony (Symfony\Component\Security\Core\User\User).
Truco
Cualquier proveedor de usuario puede cargar usuarios directamente desde la configuración especificando el parámetro de configuración users y la lista de usuarios debajo de él.
Prudencia
Si tu nombre de usuario es completamente numérico (por ejemplo, 77) o contiene un guión (por ejemplo, user-name), debes utilizar la sintaxis alterna al especificar usuarios en YAML:
users:
- { name: 77, password: pass, roles: 'ROLE_USER' }
- { name: user-name, password: pass, roles: 'ROLE_USER' }
Para sitios pequeños, este método es rápido y fácil de configurar. Para sistemas más complejos, querrás cargar usuarios desde la base de datos.
Si deseas cargar tus usuarios a través del ORM de Doctrine, lo puedes hacer creando una clase User y configurando el proveedor entity.
Truco
Hay disponible un paquete de código abierto de alta calidad, el cual te permite almacenar tus usuarios a través del ORM u ODM de Doctrine. Lee más acerca del FOSUserBundle en GitHub.
Con este enfoque, primero crea tu propia clase User, la cual se almacenará en la base de datos.
// src/Acme/UserBundle/Entity/User.php
namespace Acme\UserBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class User implements UserInterface
{
/**
* @ORM\Column(type="string", length=255)
*/
protected $username;
// ...
}
En cuanto al sistema de seguridad se refiere, el único requisito para tu clase Usuario personalizada es que implemente la interfaz Symfony\Component\Security\Core\User\UserInterface. Esto significa que el concepto de un «usuario» puede ser cualquier cosa, siempre y cuando implemente esta interfaz.
Nuevo en la versión 2.1: En Symfony 2.1, se removió el método equals de la UserInterface. Si necesitas sustituir la implementación predeterminada de la lógica de comparación, implementa la nueva interfaz Symfony\Component\Security\Core\User\EquatableInterface.
Nota
El objeto User se debe serializar y guardar en la sesión entre peticiones, por lo tanto se recomienda que implementes la interfaz Serializable en tu objeto que representa al usuario. Esto es especialmente importante si tu clase User tiene una clase padre con propiedades privadas.
A continuación, configura una entidad proveedora de usuario, y apúntala a tu clase User:
# app/config/security.yml
security:
providers:
main:
entity: { class: Acme\UserBundle\Entity\User, property: username }
<!-- app/config/security.xml -->
<config>
<provider name="main">
<entity class="Acme\UserBundle\Entity\User" property="username" />
</provider>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'providers' => array(
'main' => array(
'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
),
),
));
Con la introducción de este nuevo proveedor, el sistema de autenticación intenta cargar un objeto User de la base de datos utilizando el campo username de esa clase.
Nota
Este ejemplo sólo intenta mostrar la idea básica detrás del proveedor entity. Para ver un ejemplo completo funcionando, consulta Cómo cargar usuarios desde la base de datos con seguridad (el Proveedor de entidad).
Para más información sobre cómo crear tu propio proveedor personalizado (por ejemplo, si necesitas cargar usuarios a través de un servicio Web), consulta Cómo crear un proveedor de usuario personalizado.
Hasta ahora, por simplicidad, todos los ejemplos tienen las contraseñas de los usuarios almacenadas en texto plano (si los usuarios se almacenan en un archivo de configuración o en alguna base de datos). Por supuesto, en una aplicación real, por razones de seguridad, desearás codificar las contraseñas de los usuarios. Esto se logra fácilmente asignando la clase Usuario a una de las varias integradas en encoders. Por ejemplo, para almacenar los usuarios en memoria, pero ocultar sus contraseñas a través de sha1, haz lo siguiente:
# app/config/security.yml
security:
# ...
providers:
in_memory:
memory:
users:
ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' }
admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' }
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: sha1
iterations: 1
encode_as_base64: false
<!-- app/config/security.xml -->
<config>
<!-- ... -->
<provider name="in_memory">
<memory>
<user name="ryan" password="bb87a29949f3a1ee0559f8a57357487151281386" roles="ROLE_USER" />
<user name="admin" password="74913f5cd5f61ec0bcfdb775414c2fb3d161b620" roles="ROLE_ADMIN" />
</memory>
</provider>
<encoder class="Symfony\Component\Security\Core\User\User" algorithm="sha1" iterations="1" encode_as_base64="false" />
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
// ...
'providers' => array(
'in_memory' => array(
'memory' => array(
'users' => array(
'ryan' => array('password' => 'bb87a29949f3a1ee0559f8a57357487151281386', 'roles' => 'ROLE_USER'),
'admin' => array('password' => '74913f5cd5f61ec0bcfdb775414c2fb3d161b620', 'roles' => 'ROLE_ADMIN'),
),
),
),
),
'encoders' => array(
'Symfony\Component\Security\Core\User\User' => array(
'algorithm' => 'sha1',
'iterations' => 1,
'encode_as_base64' => false,
),
),
));
Al establecer las iterations a 1 y encode_as_base64 en false, la contraseña simplemente se corre una vez a través del algoritmo sha1 y sin ninguna codificación adicional. Ahora puedes calcular el hash de la contraseña mediante programación (por ejemplo, hash('sha1', 'ryanpass')) o a través de alguna herramienta en línea como functions-online.com
Si vas a crear tus usuarios dinámicamente (y almacenarlos en una base de datos), puedes utilizar algoritmos hash aún más difíciles y, luego confiar en un objeto codificador de clave real para ayudarte a codificar las contraseñas. Por ejemplo, supongamos que tu objeto usuario es Acme\UserBundle\Entity\User (como en el ejemplo anterior). Primero, configura el codificador para ese usuario:
# app/config/security.yml
security:
# ...
encoders:
Acme\UserBundle\Entity\User: sha512
<!-- app/config/security.xml -->
<config>
<!-- ... -->
<encoder class="Acme\UserBundle\Entity\User" algorithm="sha512" />
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
// ...
'encoders' => array(
'Acme\UserBundle\Entity\User' => 'sha512',
),
));
En este caso, estás utilizando el algoritmo SHA512 fuerte. Además, puesto que hemos especificado simplemente el algoritmo (sha512) como una cadena, el sistema de manera predeterminada revuelve tu contraseña 5000 veces en una fila y luego la codifica como base64. En otras palabras, la contraseña ha sido fuertemente ofuscada por lo tanto la contraseña revuelta no se puede decodificar (es decir, no se puede determinar la contraseña desde la contraseña ofuscada).
Nuevo en la versión 2.2: A partir de Symfony 2.2 también puedes utilizar los codificadores de contraseña PBKDF2 y BCrypt.
Si tienes algún formulario de registro para los usuarios, necesitas poder determinar el algoritmo de codificación utilizado en la contraseña, para que lo puedas usar en tu usuario. No importa qué algoritmo configures para tu objeto usuario, desde un controlador siempre puedes determinar el algoritmo de codificación de la contraseña de la siguiente manera:
$factory = $this->get('security.encoder_factory');
$user = new Acme\UserBundle\Entity\User();
$encoder = $factory->getEncoder($user);
$password = $encoder->encodePassword('ryanpass', $user->getSalt());
$user->setPassword($password);
Después de la autenticación, el objeto Usuario del usuario actual se puede acceder a través del servicio security.context. Desde el interior de un controlador, este se verá así:
public function indexAction()
{
$user = $this->get('security.context')->getToken()->getUser();
}
En un controlador existe un atajo para esto:
public function indexAction()
{
$user = $this->getUser();
}
Nota
Los usuarios anónimos técnicamente están autenticados, lo cual significa que el método isAuthenticated() de un objeto usuario anónimo devolverá true. Para comprobar si el usuario está autenticado realmente, verifica el rol IS_AUTHENTICATED_FULLY.
En una plantilla Twig puedes acceder a este objeto a través de la clave app.user, la cual llama al método GlobalVariables::getUser():
<p>Username: {{ app.user.username }}</p>
<p>Username: <?php echo $app->getUser()->getUsername() ?></p>
Cada mecanismo de autenticación (por ejemplo, la autenticación HTTP, formulario de acceso, etc.) utiliza exactamente un proveedor de usuario, y de forma predeterminada utilizará el primer proveedor de usuario declarado. Pero, si deseas especificar unos cuantos usuarios a través de la configuración y el resto de los usuarios en la base de datos? Esto es posible creando un nuevo proveedor que encadene los dos:
# app/config/security.yml
security:
providers:
chain_provider:
chain:
providers: [in_memory, user_db]
in_memory:
memory:
users:
foo: { password: test }
user_db:
entity: { class: Acme\UserBundle\Entity\User, property: username }
<!-- app/config/security.xml -->
<config>
<provider name="chain_provider">
<chain>
<provider>in_memory</provider>
<provider>user_db</provider>
</chain>
</provider>
<provider name="in_memory">
<memory>
<user name="foo" password="test" />
</memory>
</provider>
<provider name="user_db">
<entity class="Acme\UserBundle\Entity\User" property="username" />
</provider>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'providers' => array(
'chain_provider' => array(
'chain' => array(
'providers' => array('in_memory', 'user_db'),
),
),
'in_memory' => array(
'memory' => array(
'users' => array(
'foo' => array('password' => 'test'),
),
),
),
'user_db' => array(
'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
),
),
));
Ahora, todos los mecanismos de autenticación utilizan el chain_provider, puesto que es el primero especificado. El chain_provider, a su vez, intenta cargar el usuario, tanto el proveedor in_memory cómo USER_DB.
Truco
Si no tienes razones para separar a tus usuarios in_memory de tus usuarios user_db, lo puedes hacer aún más fácil combinando las dos fuentes en un único proveedor:
# app/config/security.yml
security:
providers:
main_provider:
memory:
users:
foo: { password: test }
entity:
class: Acme\UserBundle\Entity\User,
property: username
<!-- app/config/security.xml -->
<config>
<provider name=="main_provider">
<memory>
<user name="foo" password="test" />
</memory>
<entity class="Acme\UserBundle\Entity\User" property="username" />
</provider>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'providers' => array(
'main_provider' => array(
'memory' => array(
'users' => array(
'foo' => array('password' => 'test'),
),
),
'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
),
),
));
También puedes configurar el cortafuegos o mecanismos de autenticación individuales para utilizar un proveedor específico. Una vez más, a menos que explícitamente especifiques un proveedor, siempre se utiliza el primer proveedor:
# app/config/security.yml
security:
firewalls:
secured_area:
# ...
provider: user_db
http_basic:
realm: "Secured Demo Area"
provider: in_memory
form_login: ~
<!-- app/config/security.xml -->
<config>
<firewall name="secured_area" pattern="^/" provider="user_db">
<!-- ... -->
<http-basic realm="Secured Demo Area" provider="in_memory" />
<form-login />
</firewall>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
// ...
'provider' => 'user_db',
'http_basic' => array(
// ...
'provider' => 'in_memory',
),
'form_login' => array(),
),
),
));
En este ejemplo, si un usuario intenta acceder a través de autenticación HTTP, el sistema de autenticación debe utilizar el proveedor de usuario in_memory. Pero si el usuario intenta acceder a través del formulario de acceso, utilizará el proveedor USER_DB (ya que es el valor predeterminado para el servidor de seguridad en su conjunto).
Para más información acerca de los proveedores de usuario y la configuración del cortafuegos, consulta la Referencia de configuración de Security.
La idea de un «rol» es clave para el proceso de autorización. Cada usuario tiene asignado un conjunto de roles y cada recurso requiere uno o más roles. Si el usuario tiene los roles necesarios, se le concede acceso. En caso contrario se deniega el acceso.
Los roles son bastante simples, y básicamente son cadenas que puedes inventar y utilizar cuando sea necesario (aunque los roles son objetos internos). Por ejemplo, si necesitas comenzar a limitar el acceso a la sección admin del blog de tu sitio web, puedes proteger esa sección con un rol llamado ROLE_BLOG_ADMIN. Este rol no necesita estar definido en ningún lugar —puedes comenzar a usarlo.
Nota
Todos los roles deben comenzar con el prefijo ROLE_ el cual será gestionado por Symfony2. Si defines tus propios roles con una clase Role dedicada (más avanzada), no utilices el prefijo ROLE_.
En lugar de asociar muchos roles a los usuarios, puedes definir reglas de herencia creando una jerarquía de roles:
# app/config/security.yml
security:
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
<!-- app/config/security.xml -->
<config>
<role id="ROLE_ADMIN">ROLE_USER</role>
<role id="ROLE_SUPER_ADMIN">ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH</role>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'role_hierarchy' => array(
'ROLE_ADMIN' => 'ROLE_USER',
'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'),
),
));
En la configuración anterior, los usuarios con rol ROLE_ADMIN también tendrán el rol de ROLE_USER. El rol ROLE_SUPER_ADMIN tiene ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH y ROLE_USER (heredado de ROLE_ADMIN).
Por lo general, también quieres que tus usuarios puedan salir. Afortunadamente, el cortafuegos puede manejar esto automáticamente cuando activas el parámetro de configuración logout:
# app/config/security.yml
security:
firewalls:
secured_area:
# ...
logout:
path: /logout
target: /
# ...
<!-- app/config/security.xml -->
<config>
<firewall name="secured_area" pattern="^/">
<!-- ... -->
<logout path="/logout" target="/" />
</firewall>
<!-- ... -->
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
// ...
'logout' => array('path' => 'logout', 'target' => '/'),
),
),
// ...
));
Una vez lo hayas configurado en tu cortafuegos, enviar a un usuario a /logout (o cualquiera que sea tu path configurada), desautenticará al usuario actual. El usuario será enviado a la página de inicio (el valor definido por el parámetro target). Ambos parámetros path y target por omisión se configuran a lo que esté especificado aquí. En otras palabras, a menos que necesites personalizarlos, los puedes omitir por completo y abreviar tu configuración:
logout: ~
<logout />
'logout' => array(),
Ten en cuenta que no es necesario implementar un controlador para la URL /logout porque el cortafuegos se encarga de todo. Sin embargo, posiblemente necesites crear una ruta para que la puedas utilizar para generar la URL:
Prudencia
A partir de Symfony 2.1 debes tener una ruta que corresponda a la ruta para cerrar la sesión. Sin esta ruta, el cierre de sesión no trabajará.
# app/config/routing.yml
logout:
path: /logout
<!-- app/config/routing.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="logout" path="/logout" />
</routes>
// app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('logout', new Route('/logout', array()));
return $collection;
Una vez que el usuario ha cerrado la sesión, será redirigido a cualquier ruta definida por el parámetro target anterior (por ejemplo, la página principal). Para más información sobre cómo configurar el cierre de sesión, consulta la Referencia para afinar el sistema de seguridad.
Si dentro de una plantilla deseas comprobar si el usuario actual tiene un rol, utiliza la función ayudante incorporada:
{% if is_granted('ROLE_ADMIN') %}
<a href="...">Delete</a>
{% endif %}
<?php if ($view['security']->isGranted('ROLE_ADMIN')): ?>
<a href="...">Delete</a>
<?php endif; ?>
Nota
Si utilizas esta función y no estás en una URL donde haya un cortafuegos activo, se lanzará una excepción. Una vez más, casi siempre es buena idea tener un cortafuegos principal que cubra todas las URL (como hemos mostrado en este capítulo).
Si deseas comprobar en tu controlador si el usuario actual tiene un rol, utiliza el método isGranted() del contexto de seguridad:
public function indexAction()
{
// a los usuarios 'admin' les muestra diferente contenido
if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
// ... aquí carga el contenido 'admin'
}
// ... aquí carga otro contenido regular
}
Nota
Debe haber un cortafuegos activo o al llamar al método isGranted se producirá una excepción. Ve la nota anterior acerca de las plantillas para más detalles.
A veces, es útil poder cambiar de un usuario a otro sin tener que iniciar sesión de nuevo (por ejemplo, cuando depuras o tratas de entender un error que un usuario ve y que no se puede reproducir). Esto se puede hacer fácilmente activando el escucha switch_user del cortafuegos:
# app/config/security.yml
security:
firewalls:
main:
# ...
switch_user: true
<!-- app/config/security.xml -->
<config>
<firewall>
<!-- ... -->
<switch-user />
</firewall>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'main'=> array(
// ...
'switch_user' => true
),
),
));
Para cambiar a otro usuario, sólo tienes que añadir una cadena de consulta con el parámetro _switch_user y el nombre de usuario como el valor de la dirección actual:
http://ejemplo.com/somewhere?_switch_user=thomas
Para volver al usuario original, utiliza el nombre de usuario especial _exit:
http://ejemplo.com/somewhere?_switch_user=_exit
Durante la suplantación, el usuario está provisto con una función de rol especial llamada ROLE_PREVIOUS_ADMIN. En una plantilla, por ejemplo, este rol se puede usar para mostrar un enlace para salir de la suplantación:
{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
<a href="{{ path('homepage', {_switch_user: '_exit'}) }}">Exit impersonation</a>
{% endif %}
<?php if ($view['security']->isGranted('ROLE_PREVIOUS_ADMIN')): ?>
<a
href="<?php echo $view['router']->generate('homepage', array('_switch_user' => '_exit') ?>"
>
Exit impersonation
</a>
<?php endif; ?>
Por supuesto, esta función se debe poner a disposición de un pequeño grupo de usuarios. De forma predeterminada, el acceso está restringido a usuarios que tienen el rol ROLE_ALLOWED_TO_SWITCH. El nombre de esta función se puede modificar a través de la configuración role. Para mayor seguridad, también puedes cambiar el nombre del parámetro de consulta a través de la configuración parameter:
# app/config/security.yml
security:
firewalls:
main:
# ...
switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
<!-- app/config/security.xml -->
<config>
<firewall>
<!-- ... -->
<switch-user role="ROLE_ADMIN" parameter="_want_to_be_this_user" />
</firewall>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'main'=> array(
// ...
'switch_user' => array('role' => 'ROLE_ADMIN', 'parameter' => '_want_to_be_this_user'),
),
),
));
De forma predeterminada, Symfony2 confía en una cookie (la Sesión) para persistir el contexto de seguridad del usuario. Pero si utilizas certificados o autenticación HTTP, por ejemplo, la persistencia no es necesaria ya que están disponibles las credenciales para cada petición. En ese caso, y si no es necesario almacenar cualquier otra cosa entre peticiones, puedes activar la autenticación apátrida (lo cual significa que Symfony2 jamás creará una cookie):
# app/config/security.yml
security:
firewalls:
main:
http_basic: ~
stateless: true
<!-- app/config/security.xml -->
<config>
<firewall stateless="true">
<http-basic />
</firewall>
</config>
// app/config/security.php
$container->loadFromExtension('security', array(
'firewalls' => array(
'main' => array('http_basic' => array(), 'stateless' => true),
),
));
Nota
Si utilizas un formulario de acceso, Symfony2 creará una cookie, incluso si estableces stateless a true.
Nuevo en la versión 2.2: Las clases StringUtils y SecureRandom se añadieron en Symfony 2.2
El componente de seguridad de Symfony viene con una colección de agradables utilidades relacionadas con la seguridad. Estas utilidades las usa Symfony, pero también las deberías utilizar si quieres solucionar los problemas a que están destinadas.
El tiempo que tome comparar dos cadenas depende de sus diferencias. Este lo puede utilizar un atacante cuándo las dos cadenas representan una contraseña por ejemplo; Este se conoce como Ataque temporizado.
Internamente, al comparar dos contraseñas, Symfony utiliza un algoritmo de tiempo constante; Puedes utilizar la misma estrategia en tu propio código gracias a la clase Symfony\Component\Security\Core\Util\StringUtils:
use Symfony\Component\Security\Core\Util\StringUtils;
// ¿es igual la contraseña1 a la contraseña2?
$bool = StringUtils::equals($password1, $password2);
Siempre que necesites generar un número aleatorio seguro, te animamos a utilizar la clase Symfony\Component\Security\Core\Util\SecureRandom de Symfony:
use Symfony\Component\Security\Core\Util\SecureRandom;
$generator = new SecureRandom();
$random = $generator->nextBytes(10);
El método nextBytes() regresa una cadena aleatoria compuesta del número de caracteres pasados como argumento (10 en el ejemplo anterior).
La clase SecureRandom trabaja mejor cuándo está instalado OpenSSL pero cuándo no está disponible, recae en un algoritmo interno, el cual necesita un archivo de semilla para trabajar correctamente. Sólo suministra un nombre de archivo para habilitarlo:
$generator = new SecureRandom('/alguna/ruta/para/guardar/la/semilla.txt');
$random = $generator->nextBytes(10);
Nota
También puedes acceder a una instancia aleatoria segura directamente desde el contenedor de inyección de dependencias de Symfony; Su nombre es security.secure_random.
La seguridad puede ser un tema profundo y complejo para resolverlo correctamente en tu aplicación. Afortunadamente, el componente de seguridad de Symfony sigue un modelo de seguridad bien probado en torno a la autenticación y autorización. La autenticación, siempre sucede en primer lugar, está a cargo de un cortafuegos, cuyo trabajo es determinar la identidad del usuario a través de varios métodos diferentes (por ejemplo, la autenticación HTTP, formulario de acceso, etc.) En el recetario, encontrarás ejemplos de otros métodos para manejar la autenticación, incluyendo la manera de implementar una funcionalidad «recuérdame» por medio de cookie.
Una vez que un usuario se autentica, la capa de autorización puede determinar si el usuario debe tener acceso a un recurso específico. Por lo general, los roles se aplican a URL, clases o métodos y si el usuario actual no tiene ese rol, se le niega el acceso. La capa de autorización, sin embargo, es mucho más profunda, y sigue un sistema de «voto» para que varias partes puedan determinar si el usuario actual debe tener acceso a un determinado recurso. Para saber más sobre este y otros temas busca en el recetario.