El punto de entrada: invitar a los usuarios a conectarse
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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeVuelve a entrar utilizando abraca_admin@example.com
y la contraseña tada
. Cuando vamos a/admin
, como hemos visto antes, obtenemos "Acceso denegado". Esto se debe a laaccess_control
... y al hecho de que nuestro usuario no tiene ROLE_ADMIN
.
Pero si lo cambiamos por ROLE_USER
-un rol que sí tenemos-, el acceso está garantizado:
security: | |
// ... lines 2 - 40 | |
access_control: | |
- { path: ^/admin, roles: ROLE_USER } | |
// ... lines 43 - 44 |
Y conseguimos ver unos gráficos impresionantes.
Probemos una cosa más. Cerremos la sesión, es decir, vayamos manualmente a /logout
. Ahora que no hemos iniciado la sesión, si vamos directamente a /admin
: ¿qué debería ocurrir?
Bueno, en este momento, obtenemos una gran página de error con un código de estado 401. Pero... ¡eso no es lo que queremos! Si un usuario anónimo intenta acceder a una página protegida de nuestro sitio, en lugar de un error, queremos ser súper amables e invitarle a iniciar la sesión. Como tenemos un formulario de entrada, significa que queremos redirigir al usuario a la página de entrada.
¡Hola punto de entrada!
Para saber qué hacer cuando un usuario anónimo accede a una página protegida, cada cortafuegos define algo llamado "punto de entrada". El punto de entrada de un cortafuegos es literalmente una función que dice
¡Esto es lo que debemos hacer cuando un usuario anónimo intenta acceder a una página protegida!
Cada autentificador de nuestro cortafuegos puede o no "proporcionar" un punto de entrada. Ahora mismo, tenemos dos autentificadores: nuestro LoginFormAuthenticator
personalizado y también el autentificador remember_me
:
security: | |
// ... lines 2 - 16 | |
firewalls: | |
// ... lines 18 - 20 | |
main: | |
// ... lines 22 - 23 | |
custom_authenticator: App\Security\LoginFormAuthenticator | |
// ... lines 25 - 27 | |
remember_me: | |
secret: '%kernel.secret%' | |
signature_properties: [password] | |
#always_remember_me: true | |
// ... lines 32 - 44 |
Pero ninguno de ellos proporciona un punto de entrada, por lo que, en lugar de redirigir al usuario a una página... o algo diferente, obtenemos este error genérico 401. Algunos autenticadores incorporados -como form_login
, del que hablaremos pronto- sí proporcionan un punto de entrada... y lo veremos.
Hacer de nuestro autentificador un punto de entrada
Pero, de todos modos, ninguno de nuestros autenticadores proporciona un punto de entrada... ¡así que vamos a añadir uno!
Abre nuestro autentificador: src/Security/LoginFormAuthenticator.php
. Si quieres que tu autentificador proporcione un punto de entrada, todo lo que tienes que hacer es implementar una nueva interfaz: AuthenticationEntryPointInterface
:
// ... lines 1 - 22 | |
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; | |
class LoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface | |
{ | |
// ... lines 27 - 89 | |
} |
Esto requiere que la clase tenga un nuevo método... que en realidad ya tenemos aquí abajo. Se llama start()
. Descomenta el método. Luego, dentro, muy simplemente, vamos a redirigir a la página de inicio de sesión. Voy a robar el código de arriba:
// ... lines 1 - 22 | |
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; | |
class LoginFormAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface | |
{ | |
// ... lines 27 - 83 | |
public function start(Request $request, AuthenticationException $authException = null): Response | |
{ | |
return new RedirectResponse( | |
$this->router->generate('app_login') | |
); | |
} | |
} |
¡Y listo!
En cuanto un autentificador implemente esta interfaz, el sistema de seguridad lo notará y empezará a utilizarlo. Literalmente, si un usuario anónimo intenta acceder a una página protegida, ahora llamará a nuestro método start()
... y le redirigiremos a la página de inicio de sesión.
Observa: ¡refresca! ¡Bum! Nos lleva a la página de inicio de sesión.
Un cortafuegos tiene exactamente UN punto de entrada
Pero hay una cosa importante que hay que entender sobre los puntos de entrada. Cada cortafuegos sólo puede tener uno. Piensa que en el momento en que entramos en /admin
como usuario anónimo.... no estamos intentando entrar a través de un formulario de acceso... o a través de un token de la API. Somos verdaderamente anónimos. Por eso, si tuviéramos varios autentificadores que proporcionaran cada uno un punto de entrada, nuestro cortafuegos no sabría cuál elegir. Necesita un punto de entrada para todos los casos.
Ahora mismo, como sólo uno de nuestros dos autentificadores proporciona un punto de entrada, sabe que debe utilizarlo. Pero, ¿y si no fuera así? Veamos qué pasaría. Busca tu terminal y genera un segundo autentificador personalizado:
symfony console make:auth
Crea un autentificador vacío... y llámalo DummyAuthenticator
.
¡Qué bonito! Así se creó una nueva clase llamada DummyAuthenticator
:
// ... lines 1 - 2 | |
namespace App\Security; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\HttpFoundation\Response; | |
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |
use Symfony\Component\Security\Core\Exception\AuthenticationException; | |
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; | |
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; | |
class DummyAuthenticator extends AbstractAuthenticator | |
{ | |
public function supports(Request $request): ?bool | |
{ | |
// TODO: Implement supports() method. | |
} | |
public function authenticate(Request $request): PassportInterface | |
{ | |
// TODO: Implement authenticate() method. | |
} | |
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response | |
{ | |
// TODO: Implement onAuthenticationSuccess() method. | |
} | |
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response | |
{ | |
// TODO: Implement onAuthenticationFailure() method. | |
} | |
// public function start(Request $request, AuthenticationException $authException = null): Response | |
// { | |
// /* | |
// * If you would like this class to control what happens when an anonymous user accesses a | |
// * protected page (e.g. redirect to /login), uncomment this method and make this class | |
// * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntrypointInterface. | |
// * | |
// * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point | |
// */ | |
// } | |
} |
Y también actualizó custom_authenticator
en security.yaml
para utilizar ambas clases personalizadas:
security: | |
// ... lines 2 - 16 | |
firewalls: | |
// ... lines 18 - 20 | |
main: | |
// ... lines 22 - 23 | |
custom_authenticator: | |
- App\Security\LoginFormAuthenticator | |
- App\Security\DummyAuthenticator | |
// ... lines 27 - 46 |
En la nueva clase, dentro de supports()
, devuelve false
:
// ... lines 1 - 11 | |
class DummyAuthenticator extends AbstractAuthenticator | |
{ | |
public function supports(Request $request): ?bool | |
{ | |
return false; | |
} | |
// ... lines 18 - 43 | |
} |
No... vamos a convertir esto en un autentificador real.
Si nos detuviéramos ahora mismo... e intentáramos ir a /admin
, seguiría utilizando el punto de entrada de LoginFormAuthenticator
. Pero ahora implementaAuthenticationEntryPointInterface
:
// ... lines 1 - 10 | |
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; | |
class DummyAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface | |
{ | |
// ... lines 15 - 38 | |
} |
Y luego baja... y descomenta el método start()
. Para el cuerpo, sólo dd()
un mensaje... porque esto no se ejecutará nunca:
// ... lines 1 - 12 | |
class DummyAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface | |
{ | |
// ... lines 15 - 34 | |
public function start(Request $request, AuthenticationException $authException = null): Response | |
{ | |
dd('DummyAuthenticator::start()!'); | |
} | |
} |
Ahora el cortafuegos se dará cuenta de que tenemos dos autentificadores que proporcionan un punto de entrada cada uno. Y así, cuando refresquemos cualquier página, entrará en pánico. El error dice
¡Ejecuta por ti liiiiii!
Oh, espera, en realidad dice
Como tienes varios autentificadores en el cortafuegos "principal", tienes que establecer la
entry_point
clave a uno de tus autentificadores.
Y nos dice, de forma muy útil, los dos autentificadores que tenemos. En otras palabras: nos hace elegir.
Copia la clave entry_point
... y luego, en cualquier lugar del cortafuegos, dientry_point:
y apunta al servicio LoginFormAuthenticator
:
security: | |
// ... lines 2 - 16 | |
firewalls: | |
// ... lines 18 - 20 | |
main: | |
// ... lines 22 - 23 | |
entry_point: App\Security\LoginFormAuthenticator | |
// ... lines 25 - 47 |
Técnicamente podemos apuntar a cualquier servicio que implementeAuthenticationEntryPointInterface
... pero normalmente lo pongo en mi autentificador.
Ahora... si volvemos a /admin
.... ¡funciona bien! Sabe que debe elegir el punto de entrada de LoginFormAuthenticator
.
Hablando de LoginFormAuthenticator
... um... ¡hemos estado haciendo demasiado trabajo dentro de él! Eso es culpa mía - estamos haciendo las cosas de la manera más difícil para... ya sabes... ¡"aprender"! Pero a continuación, vamos a eliminar eso y aprovechar una clase base de Symfony que nos permitirá eliminar un montón de código. También vamos a aprender algo llamado TargetPathTrait
: una forma inteligente de redirigir al usuario en caso de éxito.
I have an issue with what I tried to do.
On my website I have an admin area and a public one. On the public side, by configuration, I can let user access all pages without authentication or require them an account if public side is private.
To manage that purpose I have a special account (guest in my database) with parameters (preferred language, theme, ...) like others users but theses parameters can only be changed by the webmaster.
To simplify my code (in controllers, services, templates,...) there's no difference between special account (guest) and other users. If guest access is allowed by webmaster, I want users to be automatically connected. So I create a new authenticator (GuestAuthenticator) with SelfValidatingPassport. It works.
It seems to works but... When I visit a page as guest and I try to access admin area I want to be redirect to login page. My first idea was that my authenticator do not implement AuthenticationEntryPointInterface. But the problem is that the returned response is 403 "Access Denied" because the user is already authenticated.
The main problem I try to resolve is that anonymous user are not real user in symfony but only a string. If you have another idea for me, it will be great.
If it's not clear, just tell me.
p.s: like always a really great tuorial