Advertisement
  1. Code
  2. Coding Fundamentals
  3. Security

Cómo hacer la autenticación de usuario con el componente de seguridad de Symfony

Scroll to top
Read Time: 11 min

() translation by (you can also view the original English article)

En este artículo, aprenderá a configurar la autenticación de usuario en PHP utilizando el componente Symfony Security. Además de la autenticación, le mostraré cómo usar su autorización basada en roles, que puede ampliar de acuerdo con sus necesidades.

El componente de seguridad de Symfony

El componente de seguridad de Symfony le permite configurar funciones de seguridad como la autenticación, la autorización basada en roles, los tokens CSRF y mucho más fácilmente. De hecho, está dividido en cuatro subcomponentes, que puede elegir de acuerdo con sus necesidades.

El componente de seguridad tiene los siguientes subcomponentes:

  • symfony/security-core
  • symfony/security-http
  • symfony/security-csrf
  • symfony/security-acl

En este artículo, vamos a explorar la función de autenticación proporcionada por el componente symfony / security-core.

Como de costumbre, comenzaremos con las instrucciones de instalación y configuración, y luego exploraremos algunos ejemplos del mundo real para demostrar los conceptos clave.

Instalacion y configuracion

En esta sección, vamos a instalar el componente de seguridad de Symfony. Supongo que ya ha instalado Composer en su sistema; lo necesitaremos para instalar el componente de seguridad disponible en Packagist.

Así que adelante e instale el componente de Seguridad usando el siguiente comando.

1
$composer require symfony/security

Vamos a cargar usuarios de la base de datos MySQL en nuestro ejemplo, por lo que también necesitaremos una capa de abstracción de la base de datos. Instalemos una de las capas de abstracción de base de datos más populares: Doctrine DBAL.

1
$composer require doctrine/dbal

Eso debería haber creado el archivo composer.json, que debería verse así:

1
{
2
    "require": {
3
        "symfony/security": "^4.1",
4
        "doctrine/dbal": "^2.7"
5
    }
6
}

Modifiquemos el archivo composer.json para que se vea como el siguiente.

1
{
2
    "require": {
3
        "symfony/security": "^4.1",
4
        "doctrine/dbal": "^2.7"
5
    },
6
    "autoload": {
7
         "psr-4": {
8
             "Sfauth\\": "src"
9
         },
10
         "classmap": ["src"]
11
    }
12
}

Como hemos agregado una nueva entrada de classmap, continuemos y actualicemos el autocargador del compositor ejecutando el siguiente comando.

1
$composer dump -o

Ahora puede usar el espacio de nombres de Sfauth para autocargar clases en el directorio src.

Esa es la parte de la instalación, pero ¿cómo se supone que debes usarla? De hecho, solo se trata de incluir el archivo autoload.php creado por Composer en su aplicación, como se muestra en el siguiente fragmento.

1
<?php
2
require_once './vendor/autoload.php';
3
4
// application code

5
?>

Un ejemplo del mundo real

En primer lugar, veamos el flujo de autenticación habitual proporcionado por el componente Symfony Security.

  • Lo primero es recuperar las credenciales del usuario y crear un token no autenticado.
  • A continuación, pasaremos un token no autenticado al administrador de autenticación para su validación.
  • El administrador de autenticación puede contener diferentes proveedores de autenticación, y uno de ellos se utilizará para autenticar la solicitud del usuario actual. La lógica de cómo se autentica el usuario se define en el proveedor de autenticación.
  • El proveedor de autenticación contacta al proveedor del usuario para recuperar al usuario. Es responsabilidad del proveedor del usuario cargar usuarios desde el back-end respectivo.
  • El proveedor de usuario intenta cargar al usuario utilizando las credenciales proporcionadas por el proveedor de autenticación. En la mayoría de los casos, el proveedor del usuario devuelve el objeto del usuario que implementa la interfaz UserInterface.
  • Si se encuentra el usuario, el proveedor de autenticación devuelve un token no autenticado y puede almacenar este token para las solicitudes posteriores.

En nuestro ejemplo, vamos a hacer coincidir las credenciales del usuario con la base de datos MySQL, por lo tanto, tendremos que crear el proveedor del usuario de la base de datos. También crearemos el proveedor de autenticación de base de datos que maneja la lógica de autenticación. Y finalmente, crearemos la clase User, que implementa la interfaz UserInterface.

La clase de usuario

En esta sección, crearemos la clase de usuario que representa la entidad de usuario en el proceso de autenticación.

Continúa y crea el archivo src / User / User.php con los siguientes contenidos.

1
<?php
2
namespace Sfauth\User;
3
4
use Symfony\Component\Security\Core\User\UserInterface;
5
6
class User implements UserInterface
7
{
8
    private $username;
9
    private $password;
10
    private $roles;
11
12
    public function __construct(string $username, string $password, string $roles)
13
    {
14
        if (empty($username))
15
        {
16
            throw new \InvalidArgumentException('No username provided.');
17
        }
18
19
        $this->username = $username;
20
        $this->password = $password;
21
        $this->roles = $roles;
22
    }
23
24
    public function getUsername()
25
    {
26
        return $this->username;
27
    }
28
29
    public function getPassword()
30
    {
31
        return $this->password;
32
    }
33
34
    public function getRoles()
35
    {
36
        return explode(",", $this->roles);
37
    }
38
39
    public function getSalt()
40
    {
41
        return '';
42
    }
43
44
    public function eraseCredentials() {}
45
}

Lo importante es que la clase User debe implementar la interfaz Symfony Security UserInterface. Aparte de eso, no hay nada fuera de lo común aquí.

La clase de proveedor de base de datos

Es responsabilidad del proveedor del usuario cargar usuarios desde el back-end. En esta sección, crearemos el proveedor de usuario de la base de datos, que carga al usuario desde la base de datos MySQL.

Vamos a crear el archivo src / User / DatabaseUserProvider.php con los siguientes contenidos.

1
<?php
2
namespace Sfauth\User;
3
4
use Symfony\Component\Security\Core\User\UserProviderInterface;
5
use Symfony\Component\Security\Core\User\UserInterface;
6
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
7
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
8
use Doctrine\DBAL\Connection;
9
use Sfauth\User\User;
10
11
class DatabaseUserProvider implements UserProviderInterface
12
{
13
    private $connection;
14
15
    public function __construct(Connection $connection)
16
    {
17
        $this->connection = $connection;
18
    }
19
20
    public function loadUserByUsername($username)
21
    {
22
        return $this->getUser($username);
23
    }
24
25
    private function getUser($username)
26
    {
27
        $sql = "SELECT * FROM sf_users WHERE username = :name";
28
        $stmt = $this->connection->prepare($sql);
29
        $stmt->bindValue("name", $username);
30
        $stmt->execute();
31
        $row = $stmt->fetch();
32
33
        if (!$row['username'])
34
        {
35
            $exception = new UsernameNotFoundException(sprintf('Username "%s" not found in the database.', $row['username']));
36
            $exception->setUsername($username);
37
            throw $exception;
38
        }
39
        else
40
        {
41
            return new User($row['username'], $row['password'], $row['roles']);
42
        }
43
    }
44
45
    public function refreshUser(UserInterface $user)
46
    {
47
        if (!$user instanceof User)
48
        {
49
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
50
        }
51
52
        return $this->getUser($user->getUsername());
53
    }
54
55
    public function supportsClass($class)
56
    {
57
        return 'Sfauth\User\User' === $class;
58
    }
59
}

El proveedor del usuario debe implementar la interfaz UserProviderInterface. Estamos utilizando la doctrina DBAL para realizar las operaciones relacionadas con la base de datos. Como hemos implementado la interfaz UserProviderInterface, debemos implementar los métodos loadUserByUsername, refreshUser y supportsClass.

El método loadUserByUsername debe cargar al usuario por el nombre de usuario, y eso se hace en el método getUser. Si se encuentra el usuario, devolvemos el objeto Sfauth \ User \ User correspondiente, que implementa la interfaz UserInterface.

Por otro lado, el método refreshUser actualiza el objeto User suministrado obteniendo la última información de la base de datos.

Y finalmente, el método supportsClass comprueba si el proveedor DatabaseUserProvider es compatible con la clase de usuario suministrada.

La clase de proveedor de autenticación de base de datos

Finalmente, debemos implementar el proveedor de autenticación de usuario, que define la lógica de autenticación: cómo se autentica un usuario. En nuestro caso, tenemos que hacer coincidir las credenciales del usuario con la base de datos MySQL, y por lo tanto tenemos que definir la lógica de autenticación en consecuencia.

Continúa y crea el archivo src / User / DatabaseAuthenticationProvider.php con los siguientes contenidos.

1
<?php
2
namespace Sfauth\User;
3
4
use Symfony\Component\Security\Core\Authentication\Provider\UserAuthenticationProvider;
5
use Symfony\Component\Security\Core\User\UserProviderInterface;
6
use Symfony\Component\Security\Core\User\UserCheckerInterface;
7
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
8
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
9
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
10
use Symfony\Component\Security\Core\User\UserInterface;
11
use Symfony\Component\Security\Core\Exception\AuthenticationException;
12
13
class DatabaseAuthenticationProvider extends UserAuthenticationProvider
14
{
15
    private $userProvider;
16
17
    public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, bool $hideUserNotFoundExceptions = true)
18
    {
19
        parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions);
20
21
        $this->userProvider = $userProvider;
22
    }
23
24
    protected function retrieveUser($username, UsernamePasswordToken $token)
25
    {
26
        $user = $token->getUser();
27
28
        if ($user instanceof UserInterface)
29
        {
30
            return $user;
31
        }
32
33
        try {
34
            $user = $this->userProvider->loadUserByUsername($username);
35
36
            if (!$user instanceof UserInterface)
37
            {
38
                throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
39
            }
40
41
            return $user;
42
        } catch (UsernameNotFoundException $e) {
43
            $e->setUsername($username);
44
45
            throw $e;
46
        } catch (\Exception $e) {
47
            $e = new AuthenticationServiceException($e->getMessage(), 0, $e);
48
            $e->setToken($token);
49
            throw $e;
50
        }
51
    }
52
53
    protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token)
54
    {
55
        $currentUser = $token->getUser();
56
        
57
        if ($currentUser instanceof UserInterface)
58
        {
59
            if ($currentUser->getPassword() !== $user->getPassword())
60
            {
61
                throw new AuthenticationException('Credentials were changed from another session.');
62
            }
63
        }
64
        else
65
        {
66
            $password = $token->getCredentials();
67
68
            if (empty($password))
69
            {
70
                throw new AuthenticationException('Password can not be empty.');
71
            }
72
73
            if ($user->getPassword() != md5($password))
74
            {
75
                throw new AuthenticationException('Password is invalid.');
76
            }
77
        }
78
    }
79
}

El proveedor de autenticación DatabaseAuthenticationProvider amplía la clase abstracta UserAuthenticationProvider. Por lo tanto, debemos implementar los métodos abstractos retrieveUser y checkAuthentication.

El trabajo del método retrieveUser es cargar al usuario desde el proveedor de usuario correspondiente. En nuestro caso, utilizará el proveedor de usuario DatabaseUserProvider para cargar al usuario desde la base de datos MySQL.

Por otro lado, el método checkAuthentication realiza las comprobaciones necesarias para autenticar al usuario actual. Tenga en cuenta que he utilizado el método MD5 para el cifrado de contraseñas. Por supuesto, debe usar métodos de cifrado más seguros para almacenar las contraseñas de los usuarios.

Cómo funciona en conjunto

Hasta ahora, hemos creado todos los elementos necesarios para la autenticación. En esta sección, veremos cómo armar todo para configurar la funcionalidad de autenticación.

Continúe y cree el archivo db_auth.php y rellene con los siguientes contenidos.

1
<?php
2
require_once './vendor/autoload.php';
3
4
use Sfauth\User\DatabaseUserProvider;
5
use Symfony\Component\Security\Core\User\UserChecker;
6
use Sfauth\User\DatabaseAuthenticationProvider;
7
use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager;
8
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
9
use Symfony\Component\Security\Core\Exception\AuthenticationException;
10
11
// init doctrine db connection

12
$doctrineConnection = \Doctrine\DBAL\DriverManager::getConnection(
13
  array('url' => 'mysql://{USERNAME}:{PASSWORD}@{HOSTNAME}/{DATABASE_NAME}'), new \Doctrine\DBAL\Configuration()
14
);
15
16
// init our custom db user provider

17
$userProvider = new DatabaseUserProvider($doctrineConnection);
18
19
// we'll use default UserChecker, it's used to check additional checks like account lock/expired etc.

20
// you can implement your own by implementing UserCheckerInterface interface

21
$userChecker = new UserChecker();
22
23
// init our custom db authentication provider

24
$dbProvider = new DatabaseAuthenticationProvider(
25
    $userProvider,
26
    $userChecker,
27
    'frontend'
28
);
29
30
// init authentication provider manager

31
$authenticationManager = new AuthenticationProviderManager(array($dbProvider));
32
33
try {
34
    // init un/pw, usually you'll get these from the $_POST variable, submitted by the end user

35
    $username = 'admin';
36
    $password = 'admin';
37
38
    // get unauthenticated token

39
    $unauthenticatedToken = new UsernamePasswordToken(
40
        $username,
41
        $password,
42
        'frontend'
43
    );
44
45
    // authenticate user & get authenticated token

46
    $authenticatedToken = $authenticationManager->authenticate($unauthenticatedToken);
47
48
    // we have got the authenticated token (user is logged in now), it can be stored in a session for later use

49
    echo $authenticatedToken;
50
    echo "\n";
51
} catch (AuthenticationException $e) {
52
    echo $e->getMessage();
53
    echo "\n";
54
}

Recuerde el flujo de autenticación que se discutió al principio de este artículo: el código anterior refleja esa secuencia.

Lo primero fue recuperar las credenciales del usuario y crear un token no autenticado.

1
$unauthenticatedToken = new UsernamePasswordToken(
2
    $username,
3
    $password,
4
    'frontend'
5
);

A continuación, pasamos ese token al administrador de autenticación para su validación.

1
// authenticate user & get authenticated token

2
$authenticatedToken = $authenticationManager->authenticate($unauthenticatedToken);

Cuando se llama al método de autenticación, ocurren muchas cosas detrás de escena.

En primer lugar, el administrador de autenticación selecciona un proveedor de autenticación apropiado. En nuestro caso, es el proveedor de autenticación DatabaseAuthenticationProvider, que se seleccionará para la autenticación.

A continuación, recupera al usuario por el nombre de usuario del proveedor de usuario DatabaseUserProvider. Finalmente, el método checkAuthentication realiza las comprobaciones necesarias para autenticar la solicitud del usuario actual.

Si desea probar el script db_auth.php, deberá crear la tabla sf_users en su base de datos MySQL.

1
CREATE TABLE `sf_users` (
2
  `id` int(11) NOT NULL AUTO_INCREMENT,
3
  `username` varchar(255) NOT NULL,
4
  `password` varchar(255) NOT NULL,
5
  `roles` enum('registered','moderator','admin') DEFAULT NULL,
6
  PRIMARY KEY (`id`)
7
) ENGINE=InnoDB;
8
9
INSERT INTO `sf_users` VALUES (1,'admin','21232f297a57a5a743894a0e4a801fc3','admin');

Continúa y ejecuta el script db_auth.php para ver cómo funciona. Tras completar con éxito, debe recibir un token autenticado, como se muestra en el siguiente fragmento.

1
$php db_auth.php
2
UsernamePasswordToken(user="admin", authenticated=true, roles="admin")

Una vez que el usuario está autenticado, puede almacenar el token autenticado en la sesión para las solicitudes posteriores.

¡Y con eso, hemos completado nuestra demostración de autenticación simple!

Conclusión

Hoy, miramos el componente Symfony Security, que le permite integrar funciones de seguridad en sus aplicaciones PHP. Específicamente, hablamos sobre la función de autenticación proporcionada por el subcomponente symfony / security-core, y le mostré un ejemplo de cómo esta funcionalidad puede implementarse en su propia aplicación.

¡Siéntete libre de publicar tus pensamientos usando el feed a continuación!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.