Como dice el título, crearemos juntos la autenticación JWT tan simple utilizando la plataforma API y LEXIKJWTAuthenticationBundle. Usando, por supuesto, nuestro encantador proveedor de usuario de doctrina.
Hay demasiados tutoriales en línea sobre Symfony con JWT, y también algunos sobre la plataforma API. Pero la mayoría de ellos realmente no son útiles o son muy cortos, y la mayoría de ellos que encontré le faltaban muchas cosas. Incluso no dicen que primero debes conocer algunos conceptos y al final encuentras tantos desarrolladores confusos por eso.
O simplemente mencionan el nombre de la plataforma API y usted ingresa y nada sobre todo se trata de Symfony. Para ser honesto, tiene sentido ya que la comunidad de la plataforma API es demasiado pequeña y, como realmente se basa en Symfony, la mayoría de los problemas ya solicitados y resueltos por Symfony Community, por lo que tenemos suerte de que sea tan gran comunidad :).
Espero que esto sea diferente y si tiene alguna inquietud o actualización será muy apreciada. Además, cualquier pregunta intentará responderlas a todos.
La mejor manera de instalarlo es usar el repositorio GIT, o descargar la plataforma API como archivo .zip de GitHub.
$ git clone https://github.com/api-platform/api-platform.git apiplatform-user-auth
$ cd apiplatform-user-authAhora, en primer lugar, toda la plataforma API se ejecuta en puertos específicos, por lo que debe asegurarse de que estos puertos sean gratuitos y que nada lo escuche.
$ sudo lsof -nP | grep LISTEN $ sudo kill -9 $PROCESS_NUMBER docker-compose pulldocker-compose up -d $ docker-compose down
$ COMPOSE_HTTP_TIMEOUT=120 docker-compose up -ddocker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6389d8efb6a0 apiplatform-user-auth_h2-proxy " nginx -g 'daemon of… " About a minute ago Up About a minute 0.0.0.0:443-444- > 443-444/tcp, 80/tcp, 0.0.0.0:8443-8444- > 8443-8444/tcp apiplatform-user-auth_h2-proxy_1_a012bc894b6c
a12ff2759ca4 quay.io/api-platform/varnish " docker-varnish-entr… " 2 minutes ago Up 2 minutes 0.0.0.0:8081- > 80/tcp apiplatform-user-auth_cache-proxy_1_32d747ba8877
6c1d29d1cbdd quay.io/api-platform/nginx " nginx -g 'daemon of… " 2 minutes ago Up 2 minutes 0.0.0.0:8080- > 80/tcp apiplatform-user-auth_api_1_725cd9549081
62f69838dacb quay.io/api-platform/php " docker-entrypoint p… " 2 minutes ago Up 2 minutes 9000/tcp apiplatform-user-auth_php_1_cf09d32c3120
381384222af5 dunglas/mercure " ./mercure " 2 minutes ago Up 2 minutes 443/tcp, 0.0.0.0:1337- > 80/tcp apiplatform-user-auth_mercure_1_54363c253a34
783565efb2eb postgres:10-alpine " docker-entrypoint.s… " 2 minutes ago Up 2 minutes 0.0.0.0:5432- > 5432/tcp apiplatform-user-auth_db_1_8da243ca2865
1bc8e386bf02 quay.io/api-platform/client " /bin/sh -c 'yarn st… " 2 minutes ago Up About a minute 0.0.0.0:80- > 3000/tcp apiplatform-user-auth_client_1_1c413b4e4a5e
c22bef7a0b3f quay.io/api-platform/admin " /bin/sh -c 'yarn st… " 2 minutes ago Up About a minute 0.0.0.0:81- > 3000/tcp apiplatform-user-auth_admin_1_cfecc5c6b442$ docker-compose exec php composer require doctrine maker$ docker-compose exec php bin/console make:user
The name of the security user class (e.g. User) [User]:
> Users
Do you want to store user data in the database (via Doctrine) ? (yes/no) [yes]:
>
Enter a property name that will be the unique " display " name for the user (e.g. email, username, uuid) [email]:
> email
Will this app need to hash/check user passwords ? Choose No if passwords are not needed or will be checked/hashed by some other system (e.g. a single sign-on server).
Does this app need to hash/check user passwords ? (yes/no) [yes]:
>
The newer Argon2i password hasher requires PHP 7.2, libsodium or paragonie/sodium_compat. Your system DOES support this algorithm.
You should use Argon2i unless your production system will not support it.
Use Argon2i as your password hasher (bcrypt will be used otherwise) ? (yes/no) [yes]:
>
created: src/Entity/Users.php
created: src/Repository/UsersRepository.php
updated: src/Entity/Users.php
updated: config/packages/security.yaml
Success !
Next Steps:
- Review your new App E ntity U sers class.
- Use make:entity to add more fields to your Users entity and then run make:migration.
- Create a way to authenticate ! See https://symfony.com/doc/current/security.html # api/src/Entity/Users.php
/**
* @see UserInterface
*/ # api/src/Entity/Users.php
/**
* @ORMEntity(repositoryClass="AppRepositoryUsersRepository")
*/
class Users implements UserInterface
{
...
}Puede ver claramente en este repositorio algunas funciones preimplementadas como FindById (), pero ahora pensemos otra función que nos ayude a crear un nuevo usuario.
# api/src/Repository/UsersRepository.php
class UsersRepository extends ServiceEntityRepository
{
/** EntityManager $manager */
private $ manager ;
. . . .
}e inicializarlo en el constructor como el siguiente:
# api/src/Repository/UsersRepository.php
/**
* UsersRepository constructor.
* @param RegistryInterface $registry
*/
public function __construct ( RegistryInterface $ registry )
{
parent :: __construct ( $ registry , Users::class);
$ this -> manager = $ registry -> getEntityManager ();
} # api/src/Repository/UsersRepository.php
/**
* Create a new user
* @param $data
* @return Users
* @throws DoctrineORMORMException
* @throws DoctrineORMOptimisticLockException
*/
public function createNewUser ( $ data )
{
$ user = new Users ();
$ user -> setEmail ( $ data [ ' email ' ])
-> setPassword ( $ data [ ' password ' ]);
$ this -> manager -> persist ( $ user );
$ this -> manager -> flush ();
return $ user ;
}$ docker-compose exec php bin/console make:controller
Choose a name for your controller class (e.g. TinyJellybeanController):
> AuthController
created: src/Controller/AuthController.php
created: templates/auth/index.html.twig
Success !
Next: Open your new controller class and add some pages ! # api/src/Controller/AuthController.php
use App Repository UsersRepository ;
class AuthController extends AbstractController
{
/** @var UsersRepository $userRepository */
private $ usersRepository ;
/**
* AuthController Constructor
*
* @param UsersRepository $usersRepository
*/
public function __construct ( UsersRepository $ usersRepository )
{
$ this -> usersRepository = $ usersRepository ;
}
. . . . . . .
} # api/config/services.yaml
services :
......
# Repositories
app.user.repository :
class : AppRepositoryUsersRepository
arguments :
- SymfonyBridgeDoctrineRegistryInterface
# Controllers
app.auth.controller :
class : AppControllerAuthController
arguments :
- ' @app.user.repository ' # api/src/Controller/AuthController.php
# Import those
use Symfony Component HttpFoundation Request ;
use Symfony Component HttpFoundation Response ;
# Then add this to the class
/**
* Register new user
* @param Request $request
*
* @return Response
*/
public function register ( Request $ request )
{
$ newUserData [ ' email ' ] = $ request -> get ( ' email ' );
$ newUserData [ ' password ' ] = $ request -> get ( ' password ' );
$ user = $ this -> usersRepository -> createNewUser ( $ newUserData );
return new Response ( sprintf ( ' User %s successfully created ' , $ user -> getUsername ()));
} # src/config/routes.yaml
# Register api
register :
path : /register
controller : AppControllerAuthController::register
methods : ['POST']$ docker-compose exec php bin/console make:migration
$ docker-compose exec php bin/console doctrine:migrations:migrate
WARNING ! You are about to execute a database migration that could result in schema changes and data loss. Are you sure you wish to continue ? (y/n) y$ curl -X POST -H " Content-Type: application/json " " http://localhost:8080/[email protected]&password=test1 "
User [email protected] successfully created$ docker-compose exec db psql -U api-platform api
psql (10.8)
Type " help " for help.
$ api= # select * from users;
id | email | roles | password
----+----------------+-------+----------
6 | [email protected] | [] | test1
(1 row) # api/src/Repository/UsersRepository.php
use Symfony Component Security Core Encoder UserPasswordEncoderInterface ;
class UsersRepository extends ServiceEntityRepository
{
.......
/** UserPasswordEncoderInterface $encoder */
private $ encoder ;
/**
* UserRepository constructor.
* @param RegistryInterface $registry
* @param UserPasswordEncoderInterface $encoder
*/
public function __construct ( RegistryInterface $ registry , UserPasswordEncoderInterface $ encoder )
{
parent :: __construct ( $ registry , Users::class);
$ this -> manager = $ registry -> getEntityManager ();
$ this -> encoder = $ encoder ;
}
} # api/config/services.yaml
services :
.......
# Repositories
app.user.repository :
class : AppRepositoryUsersRepository
arguments :
- SymfonyBridgeDoctrineRegistryInterface
- SymfonyComponentSecurityCoreEncoderUserPasswordEncoderInterface # api/src/Repository/UsersRepository.php
public function createNewUser ( $ data )
{
$ user = new Users ();
$ user -> setEmail ( $ data [ ' email ' ])
-> setPassword ( $ this -> encoder -> encodePassword ( $ user , $ data [ ' password ' ]));
. . . . . . .
}$ curl -X POST -H " Content-Type: application/json " " http://localhost:8080/[email protected]&password=test2 "
User [email protected] successfully created$ api= # select * from users;
id | email | roles | password
----+----------------+-------+-------------------------------------------------------------------------------------------------
6 | [email protected] | [] | test1
7 | [email protected] | [] | $argon2i$v =19 $m =1024,t=2,p=2 $VW9tYXEzZHp5U0RMSE5ydA$bo +V1X6rgYZ4ebN/bs1cpz+sf+DQdx3Duu3hvFUII8M
(2 rows)$ docker-compose exec php composer require jwt-auth$ curl -X GET -H " Content-Type: application/json " " http://localhost:8080/greetings "
{
" @context " : " /contexts/Greeting " ,
" @id " : " /greetings " ,
" @type " : " hydra:Collection " ,
" hydra:member " : [],
" hydra:totalItems " : 0
} # api/src/Controller/AuthController.php
/**
* api route redirects
* @return Response
*/
public function api ()
{
return new Response ( sprintf ( " Logged in as %s " , $ this -> getUser ()-> getUsername ()));
} # api/config/routes.yaml
api :
path : /api
controller : AppControllerAuthController::api
methods : ['POST']Ahora, necesitamos hacer algunas configuraciones en nuestro archivo de configuración de seguridad:
# api/config/packages/security.yaml
app_user_provider :
entity :
class : AppEntityUsers
property : email # api/config/packages/security
register :
pattern : ^/register
stateless : true
anonymous : true # api/config/packages/security.yaml
main :
anonymous : false
stateless : true
provider : app_user_provider
json_login :
check_path : /login
username_path : email
password_path : password
success_handler : lexik_jwt_authentication.handler.authentication_success
failure_handler : lexik_jwt_authentication.handler.authentication_failure
guard :
authenticators :
- lexik_jwt_authentication.jwt_token_authenticator # api/config/packages/security.yaml
api :
pattern : ^/api
stateless : true
anonymous : false
provider : app_user_provider
guard :
authenticators :
- lexik_jwt_authentication.jwt_token_authenticatorComo verá en las configuraciones anteriores, establecemos el Anónimo en False, no queremos que nadie acceda a estas dos API ahora, también le dicemos al marco que el proveedor para usted es el proveedor de usuarios que definimos antes, y al final le decimos qué autenticador utilizará y los mensajes de éxito/faliure de autenticación.
Ahora, si probaste la llamada, la probamos en la parte adicional para la API /Greetings
$ curl -X GET -H " Content-Type: application/json " " http://localhost:8080/greetings "
{
" code " : 401,
" message " : " JWT Token not found "
}$ curl -X POST -H " Content-Type: application/json " " http://localhost:8080/api "
{
" code " : 401,
" message " : " JWT Token not found "
} # api/config/packages/security.yaml
... .
json_login :
check_path : /login
# api/config/routes.yaml
# Login check to log the user and generate JWT token
api_login_check :
path : /login
methods : ['POST']$ curl -X POST -H " Content-Type: application/json " http://localhost:8080/login -d ' {"email":"[email protected]","password":"test2"} '
{ " token " : " eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NTg2OTg4MTIsImV4cCI6MTU1ODcwMjQxMiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoidGVzdDJAbWFpbC5jb20ifQ.nzd5FVhcyrfjYyN8jRgYFp3VOB2QytnPPRGNyp4ZtfLx6IRwg0TWZJPu5OFtOKPkdLO8DQAr_4Fpq_G6oPjzoxmGOASNuRoQonik9FCCq6oAIW3k5utzQecXDVE_ImnfgByc6WYW6a-aWLnsq1qtvxy274ojqdR0rWLePwSWX5K5-t08zDBgavO_87dVpYd0DLwhHIS7F10lNscET7bfWS-ioPDTv-G74OvkcpbcjgwHhXlO7TYubnrES-FsvAw7kezQe4BPxdbXr1w-XBZuqTNEU4MyrBuadSLgjoe_gievNBtkVhKErIkEQZVjeJIQ4xaKaxwmPxZcP9jYkE47myRdbMsL9XHSd0XmGq0bPuGjOJ2KLTmUb5oeuRnY-e9Q_V9BbouEGw0sjw2meo6Jot2MZyv5ZnLci_GwpRtWqmV7ZLw5jNyiLDFXR1rz70NcJh7EXqu9o4nno3oc68zokfDQvGkJJJZMtBrLCK5pKGMh0a1elIz41LRLZvpLYCrOZ2f4wCkGRD_U92iILD6w8EdVWGoO1wTn5Z2k8-GS1-QH9f-4KkOpaYGPCwwdrY7yioSt2oVbEj2FOb1jULteeP_Cpu44HyJktPLPW_wrN2OtZlUFr4Vz_owDSIvNESYk1JBQ_Fjlv9QGmUs9itzaDExjfB4QYoGkvpfNymtw2PI " }Como lo ves, creó JWT Token para mí, por lo que puedo usarlo para llamar a cualquier API en la aplicación. Si muestra alguna excepción como no poder generar token para las configuraciones especificadas , consulte este paso aquí. Primero abra su archivo .env , necesitaremos el jwt_passphrase, así que mantenlo abierto
$ mkdir -p api/config/jwt
$ openssl genrsa -out api/config/jwt/private.pem -aes256 4096 # this will ask you for the JWT_PASSPHRASE
$ openssl rsa -pubout -in api/config/jwt/private.pem -out api/config/jwt/public.pem # will confirm the JWT_PASSPHRASE again$ curl -X GET -H " Content-Type: application/json " -H " Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NTg2OTg4MTIsImV4cCI6MTU1ODcwMjQxMiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoidGVzdDJAbWFpbC5jb20ifQ.nzd5FVhcyrfjYyN8jRgYFp3VOB2QytnPPRGNyp4ZtfLx6IRwg0TWZJPu5OFtOKPkdLO8DQAr_4Fpq_G6oPjzoxmGOASNuRoQonik9FCCq6oAIW3k5utzQecXDVE_ImnfgByc6WYW6a-aWLnsq1qtvxy274ojqdR0rWLePwSWX5K5-t08zDBgavO_87dVpYd0DLwhHIS7F10lNscET7bfWS-ioPDTv-G74OvkcpbcjgwHhXlO7TYubnrES-FsvAw7kezQe4BPxdbXr1w-XBZuqTNEU4MyrBuadSLgjoe_gievNBtkVhKErIkEQZVjeJIQ4xaKaxwmPxZcP9jYkE47myRdbMsL9XHSd0XmGq0bPuGjOJ2KLTmUb5oeuRnY-e9Q_V9BbouEGw0sjw2meo6Jot2MZyv5ZnLci_GwpRtWqmV7ZLw5jNyiLDFXR1rz70NcJh7EXqu9o4nno3oc68zokfDQvGkJJJZMtBrLCK5pKGMh0a1elIz41LRLZvpLYCrOZ2f4wCkGRD_U92iILD6w8EdVWGoO1wTn5Z2k8-GS1-QH9f-4KkOpaYGPCwwdrY7yioSt2oVbEj2FOb1jULteeP_Cpu44HyJktPLPW_wrN2OtZlUFr4Vz_owDSIvNESYk1JBQ_Fjlv9QGmUs9itzaDExjfB4QYoGkvpfNymtw2PI " " http://localhost:8080/greetings "
{
" @context " : " /contexts/Greeting " ,
" @id " : " /greetings " ,
" @type " : " hydra:Collection " ,
" hydra:member " : [],
" hydra:totalItems " : 0
}
$ curl -X GET -H " Content-Type: application/json " " http://localhost:8080/greetings "
{
" code " : 401,
" message " : " JWT Token not found "
}Supongo que ahora ves la diferencia.
$ curl -X POST -H " Content-Type: application/json " -H " Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NTg2OTg4MTIsImV4cCI6MTU1ODcwMjQxMiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sInVzZXJuYW1lIjoidGVzdDJAbWFpbC5jb20ifQ.nzd5FVhcyrfjYyN8jRgYFp3VOB2QytnPPRGNyp4ZtfLx6IRwg0TWZJPu5OFtOKPkdLO8DQAr_4Fpq_G6oPjzoxmGOASNuRoQonik9FCCq6oAIW3k5utzQecXDVE_ImnfgByc6WYW6a-aWLnsq1qtvxy274ojqdR0rWLePwSWX5K5-t08zDBgavO_87dVpYd0DLwhHIS7F10lNscET7bfWS-ioPDTv-G74OvkcpbcjgwHhXlO7TYubnrES-FsvAw7kezQe4BPxdbXr1w-XBZuqTNEU4MyrBuadSLgjoe_gievNBtkVhKErIkEQZVjeJIQ4xaKaxwmPxZcP9jYkE47myRdbMsL9XHSd0XmGq0bPuGjOJ2KLTmUb5oeuRnY-e9Q_V9BbouEGw0sjw2meo6Jot2MZyv5ZnLci_GwpRtWqmV7ZLw5jNyiLDFXR1rz70NcJh7EXqu9o4nno3oc68zokfDQvGkJJJZMtBrLCK5pKGMh0a1elIz41LRLZvpLYCrOZ2f4wCkGRD_U92iILD6w8EdVWGoO1wTn5Z2k8-GS1-QH9f-4KkOpaYGPCwwdrY7yioSt2oVbEj2FOb1jULteeP_Cpu44HyJktPLPW_wrN2OtZlUFr4Vz_owDSIvNESYk1JBQ_Fjlv9QGmUs9itzaDExjfB4QYoGkvpfNymtw2PI " " http://localhost:8080/api "
Logged in as [email protected]Como puede ver solo desde el token JWT, puede saber exactamente en quién se inicia sesión, y puede mejorar esto implementando propiedades aditonales del usuario como Isactive o Userroles ... etc.