clase 13 seguridad
DESCRIPTION
www.hydrascs.comTRANSCRIPT
Seguridad
Realizada por:Christian Aquino |@cj_aquinoDiego Ramirez |@thedarsideofitGonzalo Alonso |@GonzaloAlonsoDDiego Barros |@Inmzombie
Para: Hydras C&S |@hydras_csBasada en Libro Symfony 2 en español Nacho Pacheco y The Book
Seguridad etapas
Autenticación HTTP
security: firewalls: secured_area: pattern: ^/ anonymous: ~ http_basic: realm: "Secured Demo Area"
access_control: - { path: ^/admin, roles: ROLE_ADMIN }
Autenticación HTTP
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
Autenticación HTTP● Hay dos usuarios en el sistema (ryan y admin);● Los usuarios se autentican a través de la autenticación
HTTP básica del sistema;● Cualquier URL que coincida con /admin/* está
protegida, y sólo el usuario admin puede acceder a ellas;
● Todas las URL que no coincidan con /admin/* son accesibles para todos los usuarios (y nunca se pide al usuario que se registre).
Cortafuegos (Autenticación)
Cortafuegos (Autenticación)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.En este ejemplo el patrón (^/) concordará con cada petición entrante.Cualquier usuario puede acceder a /foo sin que se le pida se autentique.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 autentiqueSi eliminas la clave anonymous, el cortafuegos siempre hará que un usuario se autentique inmediatamente.
Control de acceso (Autorización)
Control de acceso (Autorización)
Si un usuario solicita /admin/foo, la configuración access_control dice que cualquier URL coincidente con el patrón de la expresión regular ^/admin (/admin o /admin/*) requiere el rol ROLE_ADMIN.Los roles son la base para la mayor parte de la autorización: solo puede acceder a /admin/foo si cuenta con el rol ROLE_ADMIN.
Control de acceso (Autorización)
Control de acceso (Autorización)
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, el usuario debe ver algún tipo de mensaje indicándole que se le ha denegado el acceso.Cuando Symfony niega el acceso al usuario, él verá una pantalla de error y recibe un código de estado HTTP 403 (Prohibido).
Control de acceso (Autorización)
Control de acceso (Autorización)Si admin solicita /admin/foo, 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. Independientemente del método de autenticación, siempre es el mismo:1. Un usuario accede a un recurso protegido2. Se redirige al usuario al formulario de acceso3. El usuario presenta sus credenciales
(usuario/contraseña)4. El cortafuegos autentica al usuario5. El nuevo usuario autenticado intenta de nuevo la
petición original.
Usando un formulario de acceso tradicional
activa el formulario de acceso en el cortafuegos:
# app/config/security.ymlsecurity: firewalls: secured_area: pattern: ^/ anonymous: ~ form_login: login_path: login check_path: login_check
Usando un formulario de acceso tradicional
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: ~
Configurar rutas
# app/config/routing.ymllogin: pattern: /login defaults: { _controller: AcmeSecurityBundle:Security:login }login_check: pattern: /login_check
Controlador// 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();
Controlador // 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);
Controlador }
return $this->render( 'AcmeSecurityBundle:Security:login.html.twig', array( // último nombre de usuario ingresado 'last_username' => $session->get(
SecurityContext::LAST_USERNAME), 'error' => $error, ) ); }}
Controlador
El trabajo es mostrar el formulario al usuario y los errores de ingreso que puedan haber ocurrido, el propio sistema de seguridad se encarga de verificar el nombre de usuario y contraseña y la autenticación del usuario.
Plantilla{% 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" /> <button type="submit">login</button></form>
PlantillaRequisitos: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.El sistema de seguridad espera que los campos presentados se llamen _username y _password.Proceso:1. Intenta acceder2. El cortafuegos inicia la autenticación (/login)3. /login reproduce el formulario4. El usuario envía el formulario de acceso a /login_check5. El sistema de seguridad intercepta la petición, comprueba
las credenciales, autentica al usuario si todo está correcto, y si no, envía al usuario de nuevo al formulario de acceso.
Evitando errores comunes1. Crea las rutas correctas
2. Asegúrate de que la página de inicio de sesión no esté protegida
3. Asegúrate de que /login_check está detrás de un cortafuegos
4. Múltiples cortafuegos no comparten el contexto de seguridad
Autorizando
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.
Protegiendo patrones de URL
# app/config/security.ymlsecurity: # ... access_control: - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN } - { path: ^/admin, roles: ROLE_ADMIN }
^/admin/users/admin/users/users/admin
Entendiendo como trabaja access_control
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:
# app/config/security.ymlsecurity: # ... access_control: - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN } - { path: ^/admin, roles: ROLE_ADMIN }
Emparejando opciones
Symfony2 crea una instancia de Symfony\Component\HttpFoundation\RequestMatcher para cada entrada access_control.
Las siguientes opciones de access_control se utilizan para emparejar:
● path● ip● host● methods
Emparejando opciones# app/config/security.ymlsecurity: # ... 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 }
1. { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }2. { path: ^/admin, roles: ROLE_USER_HOST, host: symfony.com }3. { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }4. { path: ^/admin, roles: ROLE_USER }
1. { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }2. { path: ^/admin, roles: ROLE_USER_HOST, host: symfony.com }3. { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }4. { path: ^/admin, roles: ROLE_USER }
1. { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }2. { path: ^/admin, roles: ROLE_USER_HOST, host: symfony.com }3. { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }4. { path: ^/admin, roles: ROLE_USER }
Forzando el accesoAl decidir que entrada access_control concuerda (si la hay), aplica las restricciones de acceso basándose en las opciones roles y requires_channel:● role Si el usuario no tiene determinado rol o roles, entonces
el acceso es denegado (internamente, se lanza una Symfony\Component\Security\Core\Exception\AccessDeniedException);
● requires_channel Si el canal de la petición entrante (p. ej. http) no concuerda con este valor (p. ej. https), el usuario será redirigido (p. ej. redirigido de http a https, o viceversa).
Si el acceso es denegado, el sistema intentará autenticar al usuario si aún no lo está. Si el usuario ya inició sesión, se mostrará la página del error 403 «acceso denegado».
Protegiendo por IP
# app/config/security.ymlsecurity: # ... access_control:
- { path: ^/esi, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
- { path: ^/esi, roles: ROLE_NO_ACCESS }
Protegiendo por canal
# app/config/security.ymlsecurity: # ... access_control: - {
path: ^/cart/checkout,roles: IS_AUTHENTICATED_ANONYMOUSLY,requires_channel: https
}
Protegiendo 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(); } // ...}
Protegiendo un controlador usando anotaciones
// ...use JMS\SecurityExtraBundle\Annotation\Secure;
/** * @Secure(roles="ROLE_ADMIN") */public function helloAction($name){ // ...}
Usuarios
De donde provienen los usuarios? (Proveedores de usuarios)El usuario envía un conjunto de credenciales (username y password). El trabajo del sistema de autenticación es concordar esas credenciales contra una piscina de usuarios.Los usuarios pueden venir de cualquier parte. Todo lo que proporcione usuarios a la autenticación se llama «proveedor de usuario».Proveedores de usuario más comunes:Carga usuarios de un archivo de configuración.Carga usuarios de una tabla de la base de datos.
Especificando usuarios en un archivo de configuración (en memoria)
# app/config/security.ymlsecurity: # ... providers: default_provider: memory: users: ryan: {
password: ryanpass, roles: 'ROLE_USER' } admin: {
password: kitten, roles: 'ROLE_ADMIN' }
Especificando usuarios en un archivo de configuración (en memoria)
prudencia nombre de usuario utilizar la sintaxis alterna
users: - { name: 77, password: pass, roles: 'ROLE_USER' } - { name: user-name, password: pass, roles: 'ROLE_USER' }
Cargando usuarios de 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.
Restringiendo el acceso# app/config/security.ymlsecurity:
firewalls:frontend:
pattern: ^/*anonymous: ~form_login: ~
access_control:- { path: ^/usuario/login, roles:IS_AUTHENTICATED_ANONYMOUSLY }
Restringiendo el acceso- { path: ^/usuario/registro, roles:IS_AUTHENTICATED_ANONYMOUSLY }- { path: ^/usuario/*, roles: ROLE_USUARIO }
providers:usuarios:
entity: {class: Clase\UsuarioBundle\Entity\Usuario,property: email }
encoders:Clase\UsuarioBundle\Entity\Usuario: {algorithm: sha512, iterations: 10 }
Creando la entidad usuario$ php app/console doctrine:generate:entityThe Entity shortcut name: UsuarioBundle:UsuarioConfiguration format (yml, xml, php, or annotation) [annotation]:
New field name (): nombreField type [string]: <Enter>
New field name (): apellidosField type [string]: <Enter>
New field name (): emailField type [string]: <Enter>
New field name (): passwordField type [string]: <Enter>
New field name (): saltField type [string]: <Enter>
New field name (): <Enter>Do you want to generate an empty repository class [no]? noDo you confirm generation [yes]? yes
Creando el proveedor de usuarios// src/Clase/UsuarioBundle/Entity/Usuario.phpnamespace Clase\UsuarioBundle\Entity;use Symfony\Component\Security\Core\User\UserInterface;use Doctrine\ORM\Mapping as ORM;/*** Cupon\UsuarioBundle\Entity\Usuario** @ORM\Entity*/class Usuario implements UserInterface{...}
// src/Clase/UsuarioBundle/Entity/Usuario.phpclass Usuario implements UserInterface{function eraseCredentials() {}function getRoles(){return array('ROLE_USUARIO');}function getUsername(){return $this->getEmail();}// ...}
Implementar UserInterface
• eraseCredentials(), se invoca cuando la aplicación necesita borrar la información más sensible del usuario, como por ejemplo su contraseña.• getPassword(), se invoca cada vez que la aplicación necesita obtener la contraseña del usuario.• getRoles(), cuando se autentica a un usuario, se invoca este método para obtener un array con todos los roles que posee.• getSalt(), devuelve el valor que se utilizó para aleatorizar la contraseña cuando se creó el usuario.• getUsername(), se invoca para obtener el login o nombre de usuario que se utiliza para autenticar a los usuarios.
Añadiendo el formulario de login
usuario_login:pattern: /usuario/logindefaults: { _controller: UsuarioBundle:Default:login }
usuario_login_check:pattern: /usuario/login_check
usuario_logout:pattern: /usuario/logout
Añadiendo el formulario de login
• /login, se utiliza para mostrar el formulario de login.
• /login_check, es la acción que comprueba que el usuario y contraseña introducidos son correctos.
• /logout, se emplea para desconectar al usuario logueado.
Actualizar ruta form_login# app/config/security.ymlsecurity:
firewalls:frontend:
pattern: ^/*anonymous: ~form_login:
login_path: /usuario/logincheck_path: /usuario/login_checklogin_path: usuario_logincheck_path: usuario_login_check
El controlador//src/Clase/UsuarioBundle/Controller/DefaultController.phpnamespace Clase\UsuarioBundle\Controller;use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Symfony\Component\Security\Core\SecurityContext;class DefaultController extends Controller{public function loginAction(){
$peticion = $this->getRequest();$sesion = $peticion->getSession();$error = $peticion->attributes->get(
El controladorSecurityContext::AUTHENTICATION_ERROR,$sesion->get(SecurityContext::AUTHENTICATION_ERROR)
);return $this->render('UsuarioBundle:Default:login.html.twig', array(
'last_username' => $sesion->get(SecurityContext::LAST_USERNAME),'error' => $error
));}}
La vista del formulario{# src/Clase/UsuarioBundle/Resources/views/Default/login.html.twig #}{% if error %}
<div>{{ error.message }}</div>{% endif %}<form action="{{ path('usuario_login_check') }}" method="post">
<label for="username">Usuario:</label><input type="text" id="username" name="_username"value="{{ last_username }}" /><label for="password">Contraseña:</label><input type="password" id="password" name="_password" /><input type="submit" name="login" value="Acceder" />
</form>
Formulario de registro
#routing.yml
usuario_registro:pattern: /registrodefaults: { _controller: UsuarioBundle:Default:registro }
Controlador registro
// src/Clase/UsuarioBundle/Controller/DefaultController.phpnamespace Clase\UsuarioBundle\Controller;use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Clase\UsuarioBundle\Entity\Usuario;use Clase\UsuarioBundle\Form\Frontend\UsuarioType;
class DefaultController extends Controller{// ...
Controlador registropublic function registroAction(){
$usuario = new Usuario();$formulario = $this->createForm(new UsuarioType(), $usuario);return $this->render(
'UsuarioBundle:Default:registro.html.twig',array('formulario' => $formulario->createView())
);}}
Plantilla registro
{# src/Clase/UsuarioBundle/Resources/views/Default/registro.html.twig #}
<form action="{{ path('usuario_registro') }}" method="post"{{ form_enctype(formulario) }}>
{{ form_widget(formulario) }}<input class="boton" type="submit"
value="Registrarme" /></form>
Formulario registro
// src/Clase/UsuarioBundle/Form/Frontend/UsuarioType.phpnamespace Clase\UsuarioBundle\Form\Frontend;use Symfony\Component\Form\AbstractType;use Symfony\Component\Form\FormBuilderInterface;use Symfony\Component\OptionsResolver\OptionsResolverInterface;class UsuarioType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('nombre')->add('apellidos')->add('email', 'email')->add('password', 'repeated', array(
'type' => 'password','invalid_message' => 'Las dos contraseñas deben coincidir','options' => array('label' => 'Contraseña')
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver){
$resolver->setDefaults(array('data_class' =>'Clase\UsuarioBundle\Entity\Usuario'
));}public function getName(){
return 'clase_usuariobundle_usuariotype';}
}
Controlador registro procesar formpublic function registroAction(){
$peticion = $this->getRequest();$usuario = new Usuario();$formulario = $this->createForm(new UsuarioType(), $usuario);if ($peticion->getMethod() == 'POST') {
$formulario->bind($peticion);if ($formulario->isValid()) {
$encoder = $this->get('security.encoder_factory')->getEncoder($usuario);
$usuario->setSalt(md5(time()));$passwordCodificado =
$encoder->encodePassword($usuario->getPassword(),$usuario->getSalt()
);$usuario->setPassword($passwordCodificado);
$em = $this->getDoctrine()->getEntityManager();$em->persist($usuario);$em->flush();
$token = new UsernamePasswordToken($usuario,$usuario->getPassword(),'usuarios',$usuario->getRoles()
);$this->container
->get('security.context')->setToken($token);
return $this->redirect($this->generateUrl('url'));}
}
return $this->render('UsuarioBundle:Default:registro.html.twig',array('formulario' => $formulario->createView())
);}}