diff --git a/_build/redirection_map b/_build/redirection_map index 5fbe425b16e..a82a559aeeb 100644 --- a/_build/redirection_map +++ b/_build/redirection_map @@ -408,3 +408,4 @@ /profiler/matchers /profiler /profiler/profiling_data /profiler /profiler/wdt_follow_ajax /profiler +/security/entity_provider /security/user_provider diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 095076a039b..c33f89d94e8 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -234,29 +234,10 @@ statically using the ``dn_string`` config option. User provider ~~~~~~~~~~~~~ -Users will still be fetched from the configured user provider. If you -wish to fetch your users from a LDAP server, you will need to use the -``ldap`` user provider, in addition to one of the three authentication -providers (``form_login_ldap`` or ``http_basic_ldap`` or ``json-login-ldap``). - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/security.yaml - security: - # ... - - providers: - my_ldap_users: - ldap: - service: ldap - base_dn: 'dc=symfony,dc=com' - search_dn: '%ldap.search_dn%' - search_password: '%ldap.search_password%' - default_roles: '' - uid_key: 'uid' - filter: '(&({uid_key}={username})(objectclass=person)(ou=Users))' +Users will still be fetched from the configured user provider. If you wish to +fetch your users from a LDAP server, you will need to use the +:doc:`LDAP User Provider ` and any of these authentication +providers: ``form_login_ldap`` or ``http_basic_ldap`` or ``json_login_ldap``. Using the PBKDF2 Encoder: Security and Speed -------------------------------------------- diff --git a/security/entity_provider.rst b/security/entity_provider.rst deleted file mode 100644 index 8cd5c8cf679..00000000000 --- a/security/entity_provider.rst +++ /dev/null @@ -1,159 +0,0 @@ -.. index:: - single: Security; User provider - single: Security; Entity provider - -How to Load Security Users from the Database (the Entity Provider) -================================================================== - -Each User class in your app will usually need its own :doc:`user provider `. -If you're loading users from the database, you can use the built-in ``entity`` provider: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/security.yaml - # ... - - providers: - our_db_provider: - entity: - class: App\Entity\User - # the property to query by - e.g. username, email, etc - property: username - # if you're using multiple entity managers - # manager_name: customer - - # ... - - .. code-block:: xml - - - - - - - - - - - - - - - - .. code-block:: php - - // config/packages/security.php - use App\Entity\User; - - $container->loadFromExtension('security', [ - 'providers' => [ - 'our_db_provider' => [ - 'entity' => [ - 'class' => User::class, - 'property' => 'username', - ], - ], - ], - - // ... - ]); - -The ``providers`` section creates a "user provider" called ``our_db_provider`` that -knows to query from your ``App\Entity\User`` entity by the ``username`` property. -The name ``our_db_provider`` isn't important: it's not used, unless you have multiple -user providers and need to specify which user provider to use via the ``provider`` -key under your firewall. - -.. _authenticating-someone-with-a-custom-entity-provider: - -Using a Custom Query to Load the User -------------------------------------- - -The ``entity`` provider can only query from one *specific* field, specified by the -``property`` config key. If you want a bit more control over this - e.g. you want -to find a user by ``email`` *or* ``username``, you can do that by making your -``UserRepository`` implement a special -:class:`Symfony\\Bridge\\Doctrine\\Security\\User\\UserLoaderInterface`. This -interface only requires one method: ``loadUserByUsername($username)``:: - - // src/Repository/UserRepository.php - namespace App\Repository; - - use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; - use Doctrine\ORM\EntityRepository; - - class UserRepository extends EntityRepository implements UserLoaderInterface - { - public function loadUserByUsername($username) - { - return $this->createQueryBuilder('u') - ->where('u.username = :username OR u.email = :email') - ->setParameter('username', $username) - ->setParameter('email', $username) - ->getQuery() - ->getOneOrNullResult(); - } - } - -To finish this, remove the ``property`` key from the user provider in -``security.yaml``: - -.. configuration-block:: - - .. code-block:: yaml - - # config/packages/security.yaml - security: - # ... - - providers: - our_db_provider: - entity: - class: App\Entity\User - - .. code-block:: xml - - - - - - - - - - - - - - - .. code-block:: php - - // config/packages/security.php - use App\Entity\User; - - $container->loadFromExtension('security', [ - // ... - - 'providers' => [ - 'our_db_provider' => [ - 'entity' => [ - 'class' => User::class, - ], - ], - ], - ]); - -This tells Symfony to *not* query automatically for the User. Instead, when needed -(e.g. because ``switch_user``, ``remember_me`` or some other security feature is -activated), the ``loadUserByUsername()`` method on ``UserRepository`` will be called. diff --git a/security/user_provider.rst b/security/user_provider.rst index fd1f2103953..04e5505ec94 100644 --- a/security/user_provider.rst +++ b/security/user_provider.rst @@ -1,8 +1,7 @@ -All about User Providers -======================== +Security User Providers +======================= -Each User class in your app will usually need its own "user provider": a class -that has two jobs: +User providers are PHP classes related to Symfony Security that have two jobs: **Reload the User from the Session** At the beginning of each request (unless your firewall is ``stateless``), Symfony @@ -12,26 +11,264 @@ that has two jobs: has "changed" and de-authenticates the user if they have (see :ref:`user_session_refresh`). **Load the User for some Feature** - Some features, like ``switch_user``, ``remember_me`` and many of the built-in + Some features, like :doc:`user impersonation `, + :doc:`Remember Me ` and many of the built-in :doc:`authentication providers `, use the user provider to load a User object via its "username" (or email, or whatever field you want). Symfony comes with several built-in user providers: -.. toctree:: - :hidden: +* :ref:`Entity User Provider ` (loads users from + a database); +* :ref:`LDAP User Provider ` (loads users from a + LDAP server); +* :ref:`Memory User Provider ` (loads users from + a configuration file); +* :ref:`Chain User Provider ` (merges two or more + user providers into a new user provider). + +The built-in user providers cover all the needs for most applications, but you +can also create your own :ref:`custom user provider `. + +.. _security-entity-user-provider: + +Entity User Provider +-------------------- + +This is the most common user provider for traditional web applications. Users +are stored in a database and the user provider uses :doc:`Doctrine ` +to retrieve them: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + # ... + + providers: + users: + entity: + # the class of the entity that represents users + class: 'App\Entity\User' + # the property to query by - e.g. username, email, etc + property: 'username' + # optional: if you're using multiple Doctrine entity + # managers, this option defines which one to use + # manager_name: 'customer' + + # ... + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + use App\Entity\User; + + $container->loadFromExtension('security', [ + 'providers' => [ + 'users' => [ + 'entity' => [ + // the class of the entity that represents users + 'class' => User::class, + // the property to query by - e.g. username, email, etc + 'property' => 'username', + // optional: if you're using multiple Doctrine entity + // managers, this option defines which one to use + // 'manager_name' => 'customer', + ], + ], + ], + + // ... + ]); + +The ``providers`` section creates a "user provider" called ``users`` that knows +to query from your ``App\Entity\User`` entity by the ``username`` property. You +can choose any name for the user provider, but it's recommended to pick a +descriptive name because this will be later used in the firewall configuration. + +.. _authenticating-someone-with-a-custom-entity-provider: + +Using a Custom Query to Load the User +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``entity`` provider can only query from one *specific* field, specified by +the ``property`` config key. If you want a bit more control over this - e.g. you +want to find a user by ``email`` *or* ``username``, you can do that by making +your ``UserRepository`` implement the +:class:`Symfony\\Bridge\\Doctrine\\Security\\User\\UserLoaderInterface`. This +interface only requires one method: ``loadUserByUsername($username)``:: + + // src/Repository/UserRepository.php + namespace App\Repository; + + use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; + use Doctrine\ORM\EntityRepository; + + class UserRepository extends EntityRepository implements UserLoaderInterface + { + // ... + + public function loadUserByUsername($usernameOrEmail) + { + return $this->createQueryBuilder('u') + ->where('u.username = :query OR u.email = :query') + ->setParameter('query', $usernameOrEmail) + ->getQuery() + ->getOneOrNullResult(); + } + } + +To finish this, remove the ``property`` key from the user provider in +``security.yaml``: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + # ... + + providers: + users: + entity: + class: App\Entity\User + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + use App\Entity\User; + + $container->loadFromExtension('security', [ + // ... + + 'providers' => [ + 'users' => [ + 'entity' => [ + 'class' => User::class, + ], + ], + ], + ]); + +This tells Symfony to *not* query automatically for the User. Instead, when +needed (e.g. because :doc:`user impersonation `, +:doc:`Remember Me `, or some other security feature is +activated), the ``loadUserByUsername()`` method on ``UserRepository`` will be called. - entity_provider +.. _security-memory-user-provider: -* :doc:`entity: (load users from the database) ` -* :doc:`ldap ` -* ``memory`` (users are hardcoded in config) -* ``chain`` (try multiple user providers) +Memory User Provider +-------------------- -Or you can create a :ref:`custom user provider `. +It's not recommended to use this provider in real applications because of its +limitations and how difficult is to manage users. It may be useful in app +prototypes and for limited apps that don't store users in databases. -User providers are configured in ``config/packages/security.yaml`` under the -``providers`` key, and each has different configuration options: +This user provider stores all user information in a configuration file, +including their passwords. That's why the first step is to configure how these +users will encode their passwords: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + # ... + encoders: + # this internal class is used by Symfony to represent in-memory users + Symfony\Component\Security\Core\User\User: 'bcrypt' + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + + // this internal class is used by Symfony to represent in-memory users + use Symfony\Component\Security\Core\User\User; + + $container->loadFromExtension('security', [ + // ... + 'encoders' => [ + User::class => [ + 'algorithm' => 'bcrypt', + ], + ], + ]); + +Then, run this command to encode the plain text passwords of your users: + +.. code-block:: terminal + + $ php bin/console security:encode-password + +Now you can configure all the user information in ``config/packages/security.yaml``: .. code-block:: yaml @@ -39,37 +276,72 @@ User providers are configured in ``config/packages/security.yaml`` under the security: # ... providers: - # this becomes the internal name of the provider - # not usually important, but can be used to specify which - # provider you want for which firewall (advanced case) or - # for a specific authentication provider - some_provider_key: - - # provider type - one of the above + backend_users: memory: - # custom options for that provider users: - user: { password: '%env(USER_PASSWORD)%', roles: [ 'ROLE_USER' ] } - admin: { password: '%env(ADMIN_PASSWORD)%', roles: [ 'ROLE_ADMIN' ] } + john_admin: { password: '$2y$13$jxGxc ... IuqDju', roles: ['ROLE_ADMIN'] } + jane_admin: { password: '$2y$13$PFi1I ... rGwXCZ', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] } + +.. _security-ldap-user-provider: + +LDAP User Provider +------------------ + +This user provider requires installing certain dependencies and using some +special authentication providers, so it's explained in a separate article: +:doc:`/security/ldap`. - a_chain_provider: +.. _security-chain-user-provider: + +Chain User Provider +------------------- + +This user provider combines two or more of the other provider types (``entity``, +``memory`` and ``ldap``) to create a new user provider. The order in which +providers are configured is important because Symfony will look for users +starting from the first provider and will keep looking for in the other +providers until the user is found: + +.. code-block:: yaml + + # config/packages/security.yaml + security: + # ... + providers: + backend_users: + memory: + # ... + + legacy_users: + entity: + # ... + + users: + entity: + # ... + + all_users: chain: - providers: [some_provider_key, another_provider_key] + providers: ['legacy_users', 'users', 'backend'] .. _custom-user-provider: Creating a Custom User Provider ------------------------------- -If you're loading users from a custom location (e.g. via an API or legacy database -connection), you'll need to create a custom user provider class. First, make sure -you've followed the :doc:`Security Guide ` to create your ``User`` class. +Most applications don't need to create a custom provider. If you store users in +a database, a LDAP server or a configuration file, Symfony supports that. +However, if you're loading users from a custom location (e.g. via an API or +legacy database connection), you'll need to create a custom user provider. + +First, make sure you've followed the :doc:`Security Guide ` to create +your ``User`` class. -If you used the ``make:user`` command to create your ``User`` class (and you answered -the questions indicating that you need a custom user provider), that command will -generate a nice skeleton to get you started:: +If you used the ``make:user`` command to create your ``User`` class (and you +answered the questions indicating that you need a custom user provider), that +command will generate a nice skeleton to get you started:: - // .. src/Security/UserProvider.php + // src/Security/UserProvider.php namespace App\Security; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; @@ -132,34 +404,32 @@ generate a nice skeleton to get you started:: } } -Most of the work is already done! Read the comments in the code and update the TODO -sections to finish the user provider. - -When you're done, tell Symfony about the user provider by adding it in ``security.yaml``: +Most of the work is already done! Read the comments in the code and update the +TODO sections to finish the user provider. When you're done, tell Symfony about +the user provider by adding it in ``security.yaml``: .. code-block:: yaml # config/packages/security.yaml security: providers: - # internal name - can be anything + # the name of your user provider can be anything your_custom_user_provider: id: App\Security\UserProvider -That's it! When you use any of the features that require a user provider, your -provider will be used! If you have multiple firewalls and multiple providers, -you can specify *which* provider to use by adding a ``provider`` key under your -firewall and setting it to the internal name you gave to your user provider. +Lastly, update the ``config/packages/security.yaml`` file to set the +``provider`` key to ``your_custom_user_provider`` in all the firewalls which +will use this custom user provider. .. _user_session_refresh: Understanding how Users are Refreshed from the Session ------------------------------------------------------ -At the end of every request (unless your firewall is ``stateless``), your ``User`` -object is serialized to the session. At the beginning of the next request, it's -deserialized and then passed to your user provider to "refresh" it (e.g. Doctrine -queries for a fresh user). +At the end of every request (unless your firewall is ``stateless``), your +``User`` object is serialized to the session. At the beginning of the next +request, it's deserialized and then passed to your user provider to "refresh" it +(e.g. Doctrine queries for a fresh user). Then, the two User objects (the original from the session and the refreshed User object) are "compared" to see if they are "equal". By default, the core @@ -182,3 +452,58 @@ Or, if you need more control over the "compare users" process, make your User cl implement :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`. Then, your ``isEqualTo()`` method will be called when comparing users. +Injecting a User Provider in your Services +------------------------------------------ + +Symfony defines several services related to user providers: + +.. code-block:: terminal + + $ php bin/console debug:container user.provider + + Select one of the following services to display its information: + [0] security.user.provider.in_memory + [1] security.user.provider.in_memory.user + [2] security.user.provider.ldap + [3] security.user.provider.chain + ... + +Most of these services are abstract and cannot be injected in your services. +Instead, you must inject the normal service that Symfony creates for each of +your user providers. The names of these services follow this pattern: +``security.user.provider.concrete.``. + +For example, if you are :doc:`building a form login ` +and want to inject in your ``LoginFormAuthenticator`` a user provider of type +``memory`` and called ``backend_users``, do the following:: + + // src/Security/LoginFormAuthenticator.php + namespace App\Security; + + use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator; + use Symfony\Component\Security\Core\User\InMemoryUserProvider; + + class LoginFormAuthenticator extends AbstractFormLoginAuthenticator + { + private $userProvider; + + // change the 'InMemoryUserProvider' type-hint in the constructor if + // you are injecting a different type of user provider + public function __construct(InMemoryUserProvider $userProvider, /* ... */) + { + $this->userProvider = $userProvider; + // ... + } + } + +Then, inject the concrete service created by Symfony for the ``backend_users`` +user provider: + +.. code-block:: yaml + + # config/services.yaml + services: + # ... + + App\Security\LoginFormAuthenticator: + $userProvider: '@security.user.provider.concrete.backend_users'