Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Actualizaciones de seguridad

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Es hora de arreglar estas depreciaciones para que finalmente podamos actualizar a Symfony 6. Ve a cualquier página del sitio y haz clic en las deprecaciones de la barra de herramientas de depuración web para ver la lista. Es una lista grande... pero muchas de ellas están relacionadas con lo mismo: la seguridad.

El mayor cambio -y quizás el más maravilloso- en Symfony 5.4 y Symfony 6, es el nuevo sistema de seguridad. Pero no te preocupes. No es muy diferente del antiguo... y la ruta de actualización es sorprendentemente fácil.

UserInterface, getPassword y PasswordAuthenticatedUserInterface

Para el primer cambio, abre la entidad User. Además de UserInterface, añade un segundo PasswordAuthenticatedUserInterface. Hasta hace poco, UserInterfacetenía un montón de métodos, entre ellos getPassword()

... lines 1 - 8
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
... lines 10 - 14
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
... lines 17 - 211
}

Pero... esto no siempre tenía sentido. Por ejemplo, algunos sistemas de seguridad tienen usuarios que no tienen contraseñas. Por ejemplo, si tus usuarios se conectan a través de un sistema de inicio de sesión único, entonces no hay contraseñas que manejar. Bien, el usuario puede introducir su contraseña en ese sistema... pero en lo que respecta a nuestra aplicación, no hay contraseñas.

Para hacer esto más limpio, en Symfony 6, se eliminó getPassword() deUserInterface. Así que siempre tienes que implementar UserInterface... pero entonces el método getPassword() y su PasswordAuthenticatedUserInterfaceson opcionales.

UserInterface: getUsername() -> getUserIdentifier()

Otro cambio se refiere a getUsername(). Este método vive en UserInterface... pero su nombre siempre era confuso. Hacía parecer que era necesario tener un nombre de usuario... cuando en realidad, este método se supone que devuelve cualquier identificador de usuario único, no necesariamente un nombre de usuario. Por eso, en Symfony 6, se ha cambiado el nombre de getUsername() a getUserIdentifier(). Copia esto, pégalo, cambia getUsername por getUserIdentifier()... y ya está.

... lines 1 - 14
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
... lines 17 - 69
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
... lines 79 - 221
}

Por ahora tenemos que mantener getUsername() porque todavía estamos en Symfony 5.4... pero una vez que actualicemos a Symfony 6, podemos eliminarlo con seguridad.

Nuevo sistema de seguridad: enable_authenticator_manager

Pero el mayor cambio en el sistema de seguridad de Symfony se encuentra enconfig/packages/security.yaml. Es este enable_authenticator_manager. Cuando actualizamos la receta, nos dio esta configuración... pero estaba establecida en true

security:
... lines 2 - 9
enable_authenticator_manager: false
... lines 11 - 64

Esta pequeñísima línea de aspecto inocente nos permite cambiar del antiguo sistema de seguridad al nuevo. Y lo que esto significa, en la práctica, es que todas las formas de autenticación -como un autentificador personalizado o form_login o http_basic - comenzarán de repente a utilizar un sistema completamente nuevo bajo el capó.

En su mayor parte, si utilizas uno de los sistemas de autenticación integrados, como form_login o http_basic... probablemente no notarás ningún cambio. Puedes activar el nuevo sistema estableciendo esto como verdadero... y todo funcionará exactamente como antes.... aunque el código detrás de form_login será de repente muy diferente. En muchos sentidos, el nuevo sistema de seguridad es una refactorización interna para hacer el código del núcleo más legible y para darnos más flexibilidad, cuando la necesitemos.

Conversión Guardia -> Autenticador personalizado

Sin embargo, si tienes algún autentificador personalizado de guard... como nosotros, tendrás que convertirlo al nuevo sistema de autentificadores... que de todas formas es súper divertido... ¡así que hagámoslo!

Abre nuestro autentificador personalizado: src/Security/LoginFormAuthenticator.php. Ya podemos ver que AbstractFormLoginAuthenticator del antiguo sistema está obsoleto. Cámbialo por AbstractLoginFormAuthenticator.

... lines 1 - 20
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
... lines 22 - 23
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 26 - 107
}

Lo sé, es casi el mismo nombre: sólo hemos intercambiado "Formulario" y "Inicio de sesión". Si tu autentificador personalizado no es para un formulario de inicio de sesión, entonces cambia tu clase aAbstractAuthenticator.

Ah, y ya no necesitamos implementar PasswordAuthenticatedInterface: eso era algo para el antiguo sistema.

Añadir los nuevos métodos del autentificador

El antiguo sistema de guardia y el nuevo sistema de autentificador hacen lo mismo: averiguan quién está intentando iniciar sesión, comprueban la contraseña y deciden qué hacer en caso de éxito o fracaso. Pero el nuevo estilo de autentificador se siente bastante diferente. Por ejemplo, puedes ver inmediatamente que PhpStorm está furioso porque ahora tenemos que implementar un nuevo método llamado authenticate().

Bien, bajaré a supports(), iré a "Generar código" -o "cmd" + "N" en un Mac- e implementaré ese nuevo método authenticate(). Este es el núcleo del nuevo sistema de autentificación... y vamos a hablar de él en unos minutos.

... lines 1 - 24
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 27 - 40
public function authenticate(Request $request)
{
// TODO: Implement authenticate() method.
}
... lines 45 - 113
}

Pero los sistemas antiguo y nuevo comparten varios métodos. Por ejemplo, ambos tienen un método llamado supports()... pero el nuevo sistema tiene un tipo de retorno bool. En cuanto añadimos eso, PhpStorm se alegra.

... lines 1 - 25
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 28 - 35
public function supports(Request $request): bool
{
... lines 38 - 39
}
... lines 41 - 114
}

Abajo, en onAuthenticationSuccess(), parece que también tenemos que añadir un tipo de retorno aquí. Al final, añade el tipo Response de HttpFoundation. ¡Bien! Y mientras trabajamos en este método, cambia el nombre del argumento $providerKey por$firewallName.

... lines 1 - 25
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 28 - 90
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): Response
{
... lines 93 - 97
}
... lines 99 - 114
}

No hace falta que lo hagas, simplemente es el nuevo nombre del argumento... y es más claro.

A continuación, abajo, en onAuthenticationFailure(), añade allí también el tipo de retorno Response. Ah, y para onAuthenticationSuccess(), acabo de recordar que esto puede devolver un Response anulable. En algunos sistemas -como la autenticación con token de la API- no devolverá una respuesta.

... lines 1 - 25
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 28 - 99
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{
... lines 102 - 108
}
... lines 110 - 114
}

Por último, seguimos necesitando un método getLoginUrl(), pero en el nuevo sistema, éste acepta un argumento Request $request y devuelve un string.

... lines 1 - 25
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 28 - 110
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}

Muy bien, todavía tenemos que rellenar las "tripas", pero al menos tenemos todos los métodos que necesitamos.

Eliminación de supports() para los autentificadores de "inicio de sesión de formulario

Y de hecho, ¡podemos eliminar uno de ellos! Eliminar el método supports().

... lines 1 - 25
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 28 - 35
public function supports(Request $request): bool
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
... lines 41 - 114
}

Vale, este método sigue siendo necesario para los autentificadores personalizados y su función es la misma que antes. Pero, si saltas a la clase base, en el nuevo sistema, el métodosupports() está implementado para ti. Comprueba que la petición actual es un POST y que la URL actual es la misma que la de inicio de sesión. Básicamente, dice

Apoyo a la autentificación de esta petición si se trata de una petición POST al formulario de inicio de sesión.

Antes escribimos nuestra lógica de forma un poco diferente, pero eso es exactamente lo que estábamos comprobando.

Bien, es hora de llegar a la carne de nuestro autentificador personalizado: el método authenticate(). Hagámoslo a continuación.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.6", // v3.6.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.5
        "doctrine/annotations": "^1.13", // 1.13.2
        "doctrine/dbal": "^3.3", // 3.3.5
        "doctrine/doctrine-bundle": "^2.0", // 2.6.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.0", // 2.11.2
        "knplabs/knp-markdown-bundle": "^1.8", // 1.10.0
        "knplabs/knp-time-bundle": "^1.18", // v1.18.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.6
        "sentry/sentry-symfony": "^4.0", // 4.2.8
        "stof/doctrine-extensions-bundle": "^1.5", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.7
        "symfony/console": "6.0.*", // v6.0.7
        "symfony/dotenv": "6.0.*", // v6.0.5
        "symfony/flex": "^2.1", // v2.1.7
        "symfony/form": "6.0.*", // v6.0.7
        "symfony/framework-bundle": "6.0.*", // v6.0.7
        "symfony/mailer": "6.0.*", // v6.0.5
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/property-access": "6.0.*", // v6.0.7
        "symfony/property-info": "6.0.*", // v6.0.7
        "symfony/proxy-manager-bridge": "6.0.*", // v6.0.6
        "symfony/routing": "6.0.*", // v6.0.5
        "symfony/runtime": "6.0.*", // v6.0.7
        "symfony/security-bundle": "6.0.*", // v6.0.5
        "symfony/serializer": "6.0.*", // v6.0.7
        "symfony/stopwatch": "6.0.*", // v6.0.5
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-chartjs": "^2.0", // v2.1.0
        "symfony/validator": "6.0.*", // v6.0.7
        "symfony/webpack-encore-bundle": "^1.7", // v1.14.0
        "symfony/yaml": "6.0.*", // v6.0.3
        "symfonycasts/verify-email-bundle": "^1.7", // v1.10.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/string-extra": "^3.3", // v3.3.5
        "twig/twig": "^2.12|^3.0" // v3.3.10
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.1
        "phpunit/phpunit": "^9.5", // 9.5.20
        "rector/rector": "^0.12.17", // 0.12.20
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/maker-bundle": "^1.15", // v1.38.0
        "symfony/var-dumper": "6.0.*", // v6.0.6
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.6
        "zenstruck/foundry": "^1.16" // v1.18.0
    }
}