diff --git a/components/security.rst b/components/security.rst deleted file mode 100644 index 9985b611c63..00000000000 --- a/components/security.rst +++ /dev/null @@ -1,65 +0,0 @@ -.. index:: - single: Security - -The Security Component -====================== - - The Security component provides a complete security system for your web - application. It ships with facilities for authenticating using HTTP basic - authentication, interactive form login or X.509 certificate login, but also - allows you to implement your own authentication strategies. Furthermore, the - component provides ways to authorize authenticated users based on their - roles. - -Installation ------------- - -The Security component is divided into several smaller sub-components which can -be used separately: - -``symfony/security-core`` - It provides all the common security features, from authentication to - authorization and from encoding passwords to loading users. - -``symfony/security-http`` - It integrates the core sub-component with the HTTP protocol to handle HTTP - requests and responses. - -``symfony/security-csrf`` - It provides protection against `CSRF attacks`_. - -``symfony/security-guard`` - It brings many layers of authentication together, allowing the creation - of complex authentication systems. - -You can install each of them separately in your project: - -.. code-block:: terminal - - $ composer require symfony/security-core - $ composer require symfony/security-http - $ composer require symfony/security-csrf - $ composer require symfony/security-guard - -.. include:: /components/require_autoload.rst.inc - -.. seealso:: - - This article explains how to use the Security features as an independent - component in any PHP application. Read the :doc:`/security` article to learn - about how to use it in Symfony applications. - -Learn More ----------- - -.. toctree:: - :maxdepth: 1 - :glob: - - /components/security/* - /security - /security/* - /reference/configuration/security - /reference/constraints/UserPassword - -.. _`CSRF attacks`: https://en.wikipedia.org/wiki/Cross-site_request_forgery diff --git a/components/security/authentication.rst b/components/security/authentication.rst deleted file mode 100644 index ad89cd785c2..00000000000 --- a/components/security/authentication.rst +++ /dev/null @@ -1,334 +0,0 @@ -.. index:: - single: Security, Authentication - -Authentication -============== - -When a request points to a secured area, and one of the listeners from the -firewall map is able to extract the user's credentials from the current -:class:`Symfony\\Component\\HttpFoundation\\Request` object, it should create -a token, containing these credentials. The next thing the listener should -do is ask the authentication manager to validate the given token, and return -an *authenticated* token if the supplied credentials were found to be valid. -The listener should then store the authenticated token using -:class:`the token storage `:: - - use Symfony\Component\HttpKernel\Event\RequestEvent; - use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; - use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; - use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; - - class SomeAuthenticationListener - { - /** - * @var TokenStorageInterface - */ - private $tokenStorage; - - /** - * @var AuthenticationManagerInterface - */ - private $authenticationManager; - - /** - * @var string Uniquely identifies the secured area - */ - private $providerKey; - - // ... - - public function __invoke(RequestEvent $event) - { - $request = $event->getRequest(); - - $username = ...; - $password = ...; - - $unauthenticatedToken = new UsernamePasswordToken( - $username, - $password, - $this->providerKey - ); - - $authenticatedToken = $this - ->authenticationManager - ->authenticate($unauthenticatedToken); - - $this->tokenStorage->setToken($authenticatedToken); - } - } - -.. note:: - - A token can be of any class, as long as it implements - :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface`. - -The Authentication Manager --------------------------- - -The default authentication manager is an instance of -:class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager`:: - - use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; - use Symfony\Component\Security\Core\Exception\AuthenticationException; - - // instances of Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface - $providers = [...]; - - $authenticationManager = new AuthenticationProviderManager($providers); - - try { - $authenticatedToken = $authenticationManager - ->authenticate($unauthenticatedToken); - } catch (AuthenticationException $exception) { - // authentication failed - } - -The ``AuthenticationProviderManager``, when instantiated, receives several -authentication providers, each supporting a different type of token. - -.. note:: - - You may write your own authentication manager, the only requirement is that - it implements :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface`. - -.. _authentication_providers: - -Authentication Providers ------------------------- - -Each provider (since it implements -:class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface`) -has a :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::supports` method -by which the ``AuthenticationProviderManager`` -can determine if it supports the given token. If this is the case, the -manager then calls the provider's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::authenticate` method. -This method should return an authenticated token or throw an -:class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException` -(or any other exception extending it). - -Authenticating Users by their Username and Password -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -An authentication provider will attempt to authenticate a user based on -the credentials they provided. Usually these are a username and a password. -Most web applications store their user's username and a hash of the user's -password combined with a randomly generated salt. This means that the average -authentication would consist of fetching the salt and the hashed password -from the user data storage, hash the password the user has just provided -(e.g. using a login form) with the salt and compare both to determine if -the given password is valid. - -This functionality is offered by the :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`. -It fetches the user's data from a :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`, -uses a :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface` -to create a hash of the password and returns an authenticated token if the -password was valid:: - - use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; - use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; - use Symfony\Component\Security\Core\User\InMemoryUserProvider; - use Symfony\Component\Security\Core\User\UserChecker; - - // The 'InMemoryUser' class was introduced in Symfony 5.3. - // In previous versions it was called 'User' - $userProvider = new InMemoryUserProvider( - [ - 'admin' => [ - // password is "foo" - 'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==', - 'roles' => ['ROLE_ADMIN'], - ], - ] - ); - - // for some extra checks: is account enabled, locked, expired, etc. - $userChecker = new UserChecker(); - - // an array of password hashers (see below) - $hasherFactory = new PasswordHasherFactoryInterface(...); - - $daoProvider = new DaoAuthenticationProvider( - $userProvider, - $userChecker, - 'secured_area', - $hasherFactory - ); - - $daoProvider->authenticate($unauthenticatedToken); - -.. note:: - - The example above demonstrates the use of the "in-memory" user provider, - but you may use any user provider, as long as it implements - :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`. - It is also possible to let multiple user providers try to find the user's - data, using the :class:`Symfony\\Component\\Security\\Core\\User\\ChainUserProvider`. - -.. _the-password-encoder-factory: - -The Password Hasher Factory -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider` -uses a factory to create a password hasher for a given type of user. This allows -you to use different hashing strategies for different types of users. -The default :class:`Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactory` -receives an array of hashers:: - - use Acme\Entity\LegacyUser; - use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher; - use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory; - use Symfony\Component\Security\Core\User\InMemoryUser; - - $defaultHasher = new MessageDigestPasswordHasher('sha512', true, 5000); - $weakHasher = new MessageDigestPasswordHasher('md5', true, 1); - - $hashers = [ - InMemoryUser::class => $defaultHasher, - LegacyUser::class => $weakHasher, - // ... - ]; - $hasherFactory = new PasswordHasherFactory($hashers); - -Each hasher should implement :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface` -or be an array with a ``class`` and an ``arguments`` key, which allows the -hasher factory to construct the hasher only when it is needed. - -.. _creating-a-custom-password-encoder: - -Creating a custom Password Hasher -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There are many built-in password hasher. But if you need to create your -own, it needs to follow these rules: - -#. The class must implement :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface` - (you can also extend :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasher`); - -#. The implementations of - :method:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface::hashPassword` - and - :method:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface::isPasswordValid` - must first of all make sure the password is not too long, i.e. the password length is no longer - than 4096 characters. This is for security reasons (see `CVE-2013-5750`_), and you can use the - :method:`Symfony\\Component\\PasswordHasher\\Hasher\\CheckPasswordLengthTrait::isPasswordTooLong` - method for this check:: - - use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; - use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; - use Symfony\Component\Security\Core\Exception\BadCredentialsException; - - class FoobarHasher extends UserPasswordHasher - { - use CheckPasswordLengthTrait; - - public function hashPassword(UserInterface $user, string $plainPassword): string - { - if ($this->isPasswordTooLong($user->getPassword())) { - throw new BadCredentialsException('Invalid password.'); - } - - // ... - } - - public function isPasswordValid(UserInterface $user, string $plainPassword) - { - if ($this->isPasswordTooLong($user->getPassword())) { - return false; - } - - // ... - } - } - -.. _using-password-encoders: - -Using Password Hashers -~~~~~~~~~~~~~~~~~~~~~~ - -When the :method:`Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactory::getPasswordHasher` -method of the password hasher factory is called with the user object as -its first argument, it will return a hasher of type :class:`Symfony\\Component\\PasswordHasher\\PasswordHasherInterface` -which should be used to hash this user's password:: - - // a Acme\Entity\LegacyUser instance - $user = ...; - - // the password that was submitted, e.g. when registering - $plainPassword = ...; - - $hasher = $hasherFactory->getPasswordHasher($user); - - // returns $weakHasher (see above) - $hashedPassword = $hasher->hashPassword($user, $plainPassword); - - $user->setPassword($hashedPassword); - - // ... save the user - -Now, when you want to check if the submitted password (e.g. when trying to log -in) is correct, you can use:: - - // fetch the Acme\Entity\LegacyUser - $user = ...; - - // the submitted password, e.g. from the login form - $plainPassword = ...; - - $validPassword = $hasher->isPasswordValid($user, $plainPassword); - -Authentication Events ---------------------- - -The security component provides the following authentication events: - -=============================== ======================================================================== ============================================================================== -Name Event Constant Argument Passed to the Listener -=============================== ======================================================================== ============================================================================== -security.authentication.success ``AuthenticationEvents::AUTHENTICATION_SUCCESS`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationSuccessEvent` -security.authentication.failure ``AuthenticationEvents::AUTHENTICATION_FAILURE`` :class:`Symfony\\Component\\Security\\Core\\Event\\AuthenticationFailureEvent` -security.interactive_login ``SecurityEvents::INTERACTIVE_LOGIN`` :class:`Symfony\\Component\\Security\\Http\\Event\\InteractiveLoginEvent` -security.switch_user ``SecurityEvents::SWITCH_USER`` :class:`Symfony\\Component\\Security\\Http\\Event\\SwitchUserEvent` -security.logout_on_change ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent::class`` :class:`Symfony\\Component\\Security\\Http\\Event\\DeauthenticatedEvent` -=============================== ======================================================================== ============================================================================== - -Authentication Success and Failure Events -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When a provider authenticates the user, a ``security.authentication.success`` -event is dispatched. But beware - this event may fire, for example, on *every* -request if you have session-based authentication, if ``always_authenticate_before_granting`` -is enabled or if the token is not authenticated before AccessListener is invoked. -See ``security.interactive_login`` below if you need to do something when a user *actually* logs in. - -When a provider attempts authentication but fails (i.e. throws an ``AuthenticationException``), -a ``security.authentication.failure`` event is dispatched. You could listen on -the ``security.authentication.failure`` event, for example, in order to log -failed login attempts. - -Security Events -~~~~~~~~~~~~~~~ - -The ``security.interactive_login`` event is triggered after a user has actively -logged into your website. It is important to distinguish this action from -non-interactive authentication methods, such as: - -* authentication based on your session. -* authentication using a HTTP basic header. - -You could listen on the ``security.interactive_login`` event, for example, in -order to give your user a welcome flash message every time they log in. - -The ``security.switch_user`` event is triggered every time you activate -the ``switch_user`` firewall listener. - -The ``Symfony\Component\Security\Http\Event\DeauthenticatedEvent`` event is triggered when a token has been deauthenticated -because of a user change. It can help you perform clean-up tasks. - -.. seealso:: - - For more information on switching users, see - :doc:`/security/impersonating_user`. - -.. _`CVE-2013-5750`: https://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form diff --git a/components/security/authorization.rst b/components/security/authorization.rst deleted file mode 100644 index ffc4edc278a..00000000000 --- a/components/security/authorization.rst +++ /dev/null @@ -1,281 +0,0 @@ -.. index:: - single: Security, Authorization - -Authorization -============= - -When any of the authentication providers (see :ref:`authentication_providers`) -has verified the still-unauthenticated token, an authenticated token will -be returned. The authentication listener should set this token directly -in the :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface` -using its :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface::setToken` -method. - -From then on, the user is authenticated, i.e. identified. Now, other parts -of the application can use the token to decide whether or not the user may -request a certain URI, or modify a certain object. This decision will be made -by an instance of :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface`. - -An authorization decision will always be based on a few things: - -* The current token - For instance, the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoleNames` - method may be used to retrieve the roles of the current user (e.g. - ``ROLE_SUPER_ADMIN``), or a decision may be based on the class of the token. -* A set of attributes - Each attribute stands for a certain right the user should have, e.g. - ``ROLE_ADMIN`` to make sure the user is an administrator. -* An object (optional) - Any object for which access control needs to be checked, like - an article or a comment object. - -.. _components-security-access-decision-manager: - -Access Decision Manager ------------------------ - -Since deciding whether or not a user is authorized to perform a certain -action can be a complicated process, the standard :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager` -itself depends on multiple voters, and makes a final verdict based on all -the votes (either positive, negative or neutral) it has received. It -recognizes several strategies: - -``affirmative`` (default) - grant access as soon as there is one voter granting access; - -``consensus`` - grant access if there are more voters granting access than there are denying; - -``unanimous`` - only grant access if none of the voters has denied access. If all voters - abstained from voting, the decision is based on the ``allow_if_all_abstain`` - config option (which defaults to ``false``). - -``priority`` - grants or denies access by the first voter that does not abstain; - - .. versionadded:: 5.1 - - The ``priority`` version strategy was introduced in Symfony 5.1. - -Usage of the available options in detail:: - - use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; - - // instances of Symfony\Component\Security\Core\Authorization\Voter\VoterInterface - $voters = [...]; - - // one of "affirmative", "consensus", "unanimous", "priority" - $strategy = ...; - - // whether or not to grant access when all voters abstain - $allowIfAllAbstainDecisions = ...; - - // whether or not to grant access when there is no majority (applies only to the "consensus" strategy) - $allowIfEqualGrantedDeniedDecisions = ...; - - $accessDecisionManager = new AccessDecisionManager( - $voters, - $strategy, - $allowIfAllAbstainDecisions, - $allowIfEqualGrantedDeniedDecisions - ); - -.. seealso:: - - You can change the default strategy in the - :ref:`configuration `. - -Voters ------- - -Voters are instances -of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, -which means they have to implement a few methods which allows the decision -manager to use them: - -``vote(TokenInterface $token, $object, array $attributes)`` - this method will do the actual voting and return a value equal to one - of the class constants of :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, - i.e. ``VoterInterface::ACCESS_GRANTED``, ``VoterInterface::ACCESS_DENIED`` - or ``VoterInterface::ACCESS_ABSTAIN``; - -The Security component contains some standard voters which cover many use -cases: - -AuthenticatedVoter -~~~~~~~~~~~~~~~~~~ - -The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter` -voter supports the attributes ``IS_AUTHENTICATED_FULLY``, -``IS_AUTHENTICATED_REMEMBERED``, ``IS_AUTHENTICATED_ANONYMOUSLY``, -to grant access based on the current level of authentication, i.e. is the -user fully authenticated, or only based on a "remember-me" cookie, or even -authenticated anonymously? - -It also supports the attributes ``IS_ANONYMOUS``, ``IS_REMEMBERED``, -``IS_IMPERSONATOR`` to grant access based on a specific state of -authentication. - -.. versionadded:: 5.1 - - The ``IS_ANONYMOUS``, ``IS_REMEMBERED`` and ``IS_IMPERSONATOR`` - attributes were introduced in Symfony 5.1. - -:: - - use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; - - $trustResolver = new AuthenticationTrustResolver(); - - $authenticatedVoter = new AuthenticatedVoter($trustResolver); - - // instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface - $token = ...; - - // any object - $object = ...; - - $vote = $authenticatedVoter->vote($token, $object, ['IS_AUTHENTICATED_FULLY']); - -RoleVoter -~~~~~~~~~ - -The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter` -supports attributes starting with ``ROLE_`` and grants access to the user -when at least one required ``ROLE_*`` attribute can be found in the array of -roles returned by the token's :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoleNames` -method:: - - use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; - - $roleVoter = new RoleVoter('ROLE_'); - - $roleVoter->vote($token, $object, ['ROLE_ADMIN']); - -RoleHierarchyVoter -~~~~~~~~~~~~~~~~~~ - -The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleHierarchyVoter` -extends :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter` -and provides some additional functionality: it knows how to handle a -hierarchy of roles. For instance, a ``ROLE_SUPER_ADMIN`` role may have sub-roles -``ROLE_ADMIN`` and ``ROLE_USER``, so that when a certain object requires the -user to have the ``ROLE_ADMIN`` role, it grants access to users who in fact -have the ``ROLE_ADMIN`` role, but also to users having the ``ROLE_SUPER_ADMIN`` -role:: - - use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; - use Symfony\Component\Security\Core\Role\RoleHierarchy; - - $hierarchy = [ - 'ROLE_SUPER_ADMIN' => ['ROLE_ADMIN', 'ROLE_USER'], - ]; - - $roleHierarchy = new RoleHierarchy($hierarchy); - - $roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy); - -ExpressionVoter -~~~~~~~~~~~~~~~ - -The :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\ExpressionVoter` -grants access based on the evaluation of expressions created with the -:doc:`ExpressionLanguage component `. These -expressions have access to a number of -:ref:`special security variables `:: - - use Symfony\Component\ExpressionLanguage\Expression; - use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter; - - // Symfony\Component\Security\Core\Authorization\ExpressionLanguage; - $expressionLanguage = ...; - - // instance of Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface - $trustResolver = ...; - - // Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface - $authorizationChecker = ...; - - $expressionVoter = new ExpressionVoter($expressionLanguage, $trustResolver, $authorizationChecker); - - // instance of Symfony\Component\Security\Core\Authentication\Token\TokenInterface - $token = ...; - - // any object - $object = ...; - - $expression = new Expression( - '"ROLE_ADMIN" in role_names or (not is_anonymous() and user.isSuperAdmin())' - ); - - $vote = $expressionVoter->vote($token, $object, [$expression]); - -.. note:: - - When you make your own voter, you can use its constructor to inject any - dependencies it needs to come to a decision. - -Roles ------ - -Roles are strings that give expression to a certain right the user has (e.g. -*"edit a blog post"*, *"create an invoice"*). You can freely choose those -strings. The only requirement is that they must start with the ``ROLE_`` prefix -(e.g. ``ROLE_POST_EDIT``, ``ROLE_INVOICE_CREATE``). - -Using the Decision Manager --------------------------- - -The Access Listener -~~~~~~~~~~~~~~~~~~~ - -The access decision manager can be used at any point in a request to decide whether -or not the current user is entitled to access a given resource. One optional, -but useful, method for restricting access based on a URL pattern is the -:class:`Symfony\\Component\\Security\\Http\\Firewall\\AccessListener`, -which is one of the firewall listeners (see :ref:`firewall_listeners`) that -is triggered for each request matching the firewall map (see :ref:`firewall`). - -It uses an access map (which should be an instance of :class:`Symfony\\Component\\Security\\Http\\AccessMapInterface`) -which contains request matchers and a corresponding set of attributes that -are required for the current user to get access to the application:: - - use Symfony\Component\HttpFoundation\RequestMatcher; - use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; - use Symfony\Component\Security\Http\AccessMap; - use Symfony\Component\Security\Http\Firewall\AccessListener; - - $accessMap = new AccessMap(); - $tokenStorage = new TokenStorage(); - $requestMatcher = new RequestMatcher('^/admin'); - $accessMap->add($requestMatcher, ['ROLE_ADMIN']); - - $accessListener = new AccessListener( - $tokenStorage, - $accessDecisionManager, - $accessMap, - $authenticationManager - ); - -Authorization Checker -~~~~~~~~~~~~~~~~~~~~~ - -The access decision manager is also available to other parts of the application -via the :method:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker::isGranted` -method of the :class:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationChecker`. -A call to this method will directly delegate the question to the access -decision manager:: - - use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; - use Symfony\Component\Security\Core\Exception\AccessDeniedException; - - $authorizationChecker = new AuthorizationChecker( - $tokenStorage, - $authenticationManager, - $accessDecisionManager - ); - - if (!$authorizationChecker->isGranted('ROLE_ADMIN')) { - throw new AccessDeniedException(); - } diff --git a/components/security/firewall.rst b/components/security/firewall.rst deleted file mode 100644 index adb0fae6e4a..00000000000 --- a/components/security/firewall.rst +++ /dev/null @@ -1,164 +0,0 @@ -.. index:: - single: Security, Firewall - -The Firewall and Authorization -============================== - -Central to the Security component is authorization. This is handled by an instance -of :class:`Symfony\\Component\\Security\\Core\\Authorization\\AuthorizationCheckerInterface`. -When all steps in the process of authenticating the user have been taken successfully, -you can ask the authorization checker if the authenticated user has access to a -certain action or resource of the application:: - - use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; - use Symfony\Component\Security\Core\Exception\AccessDeniedException; - - // instance of Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface - $tokenStorage = ...; - - // instance of Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface - $authenticationManager = ...; - - // instance of Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface - $accessDecisionManager = ...; - - $authorizationChecker = new AuthorizationChecker( - $tokenStorage, - $authenticationManager, - $accessDecisionManager - ); - - // ... authenticate the user - - if (!$authorizationChecker->isGranted('ROLE_ADMIN')) { - throw new AccessDeniedException(); - } - -.. note:: - - Read the dedicated articles to learn more about :doc:`/components/security/authentication` - and :doc:`/components/security/authorization`. - -.. _firewall: - -A Firewall for HTTP Requests ----------------------------- - -Authenticating a user is done by the firewall. An application may have -multiple secured areas, so the firewall is configured using a map of these -secured areas. For each of these areas, the map contains a request matcher -and a collection of listeners. The request matcher gives the firewall the -ability to find out if the current request points to a secured area. -The listeners are then asked if the current request can be used to authenticate -the user:: - - use Symfony\Component\HttpFoundation\RequestMatcher; - use Symfony\Component\Security\Http\Firewall\ExceptionListener; - use Symfony\Component\Security\Http\FirewallMap; - - $firewallMap = new FirewallMap(); - - $requestMatcher = new RequestMatcher('^/secured-area/'); - - // array of callables - $listeners = [...]; - - $exceptionListener = new ExceptionListener(...); - - $firewallMap->add($requestMatcher, $listeners, $exceptionListener); - -The firewall map will be given to the firewall as its first argument, together -with the event dispatcher that is used by the :class:`Symfony\\Component\\HttpKernel\\HttpKernel`:: - - use Symfony\Component\HttpKernel\KernelEvents; - use Symfony\Component\Security\Http\Firewall; - - // the EventDispatcher used by the HttpKernel - $dispatcher = ...; - - $firewall = new Firewall($firewallMap, $dispatcher); - - $dispatcher->addListener( - KernelEvents::REQUEST, - [$firewall, 'onKernelRequest'] - ); - -The firewall is registered to listen to the ``kernel.request`` event that -will be dispatched by the HttpKernel at the beginning of each request -it processes. This way, the firewall may prevent the user from going any -further than allowed. - -Firewall Config -~~~~~~~~~~~~~~~ - -The information about a given firewall, such as its name, provider, context, -entry point and access denied URL, is provided by instances of the -:class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallConfig` class. - -This object can be accessed through the ``getFirewallConfig(Request $request)`` -method of the :class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallMap` class and -through the ``getConfig()`` method of the -:class:`Symfony\\Bundle\\SecurityBundle\\Security\\FirewallContext` class. - -.. _firewall_listeners: - -Firewall Listeners -~~~~~~~~~~~~~~~~~~ - -When the firewall gets notified of the ``kernel.request`` event, it asks -the firewall map if the request matches one of the secured areas. The first -secured area that matches the request will return a set of corresponding -firewall listeners (which each is a callable). -These listeners will all be asked to handle the current request. This basically -means: find out if the current request contains any information by which -the user might be authenticated (for instance the Basic HTTP authentication -listener checks if the request has a header called ``PHP_AUTH_USER``). - -Exception Listener -~~~~~~~~~~~~~~~~~~ - -If any of the listeners throws an :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`, -the exception listener that was provided when adding secured areas to the -firewall map will jump in. - -The exception listener determines what happens next, based on the arguments -it received when it was created. It may start the authentication procedure, -perhaps ask the user to supply their credentials again (when they have only been -authenticated based on a "remember-me" cookie), or transform the exception -into an :class:`Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException`, -which will eventually result in an "HTTP/1.1 403: Access Denied" response. - -Entry Points -~~~~~~~~~~~~ - -When the user is not authenticated at all (i.e. when the token storage -has no token yet), the firewall's entry point will be called to "start" -the authentication process. An entry point should implement -:class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`, -which has only one method: :method:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface::start`. -This method receives the current :class:`Symfony\\Component\\HttpFoundation\\Request` -object and the exception by which the exception listener was triggered. -The method should return a :class:`Symfony\\Component\\HttpFoundation\\Response` -object. This could be, for instance, the page containing the login form or, -in the case of Basic HTTP authentication, a response with a ``WWW-Authenticate`` -header, which will prompt the user to supply their username and password. - -Flow: Firewall, Authentication, Authorization ---------------------------------------------- - -Hopefully you can now see a little bit about how the "flow" of the security -context works: - -#. The Firewall is registered as a listener on the ``kernel.request`` event; -#. At the beginning of the request, the Firewall checks the firewall map - to see if any firewall should be active for this URL; -#. If a firewall is found in the map for this URL, its listeners are notified; -#. Each listener checks to see if the current request contains any authentication - information - a listener may (a) authenticate a user, (b) throw an - ``AuthenticationException``, or (c) do nothing (because there is no - authentication information on the request); -#. Once a user is authenticated, you'll use :doc:`/components/security/authorization` - to deny access to certain resources. - -Read the next articles to find out more about :doc:`/components/security/authentication` -and :doc:`/components/security/authorization`. diff --git a/components/security/secure_tools.rst b/components/security/secure_tools.rst deleted file mode 100644 index a9d6e0fec3a..00000000000 --- a/components/security/secure_tools.rst +++ /dev/null @@ -1,56 +0,0 @@ -Securely Generating Random Values -================================= - -The Symfony Security component comes with a collection of nice utilities -related to security. These utilities are used by Symfony, but you should -also use them if you want to solve the problem they address. - -.. note:: - - The functions described in this article were introduced in PHP 5.6 or 7. - For older PHP versions, a polyfill is provided by the - `Symfony Polyfill Component`_. - -Comparing Strings -~~~~~~~~~~~~~~~~~ - -The time it takes to compare two strings depends on their differences. This -can be used by an attacker when the two strings represent a password for -instance; it is known as a `Timing attack`_. - -When comparing two passwords, you should use the :phpfunction:`hash_equals` -function:: - - if (hash_equals($knownString, $userInput)) { - // ... - } - -Generating a Secure Random String -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Whenever you need to generate a secure random string, you are highly -encouraged to use the :phpfunction:`random_bytes` function:: - - $random = random_bytes(10); - -The function returns a random string, suitable for cryptographic use, of -the number bytes passed as an argument (10 in the above example). - -.. tip:: - - The ``random_bytes()`` function returns a binary string which may contain - the ``\0`` character. This can cause trouble in several common scenarios, - such as storing this value in a database or including it as part of the - URL. The solution is to hash the value returned by ``random_bytes()`` with - a hashing function such as :phpfunction:`md5` or :phpfunction:`sha1`. - -Generating a Secure Random Number -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you need to generate a cryptographically secure random integer, you should -use the :phpfunction:`random_int` function:: - - $random = random_int(1, 10); - -.. _`Timing attack`: https://en.wikipedia.org/wiki/Timing_attack -.. _`Symfony Polyfill Component`: https://github.com/symfony/polyfill diff --git a/controller/argument_value_resolver.rst b/controller/argument_value_resolver.rst index ebc59a02bf5..da212517f0c 100644 --- a/controller/argument_value_resolver.rst +++ b/controller/argument_value_resolver.rst @@ -51,7 +51,7 @@ In addition, some components and official bundles provide other value resolvers: Injects the object that represents the current logged in user if type-hinted with ``UserInterface``. Default value can be set to ``null`` in case the controller can be accessed by anonymous users. It requires installing - the :doc:`Security component `. + the :doc:`SecurityBundle `. ``Psr7ServerRequestResolver`` Injects a `PSR-7`_ compliant version of the current request if type-hinted diff --git a/introduction/from_flat_php_to_symfony.rst b/introduction/from_flat_php_to_symfony.rst index b2840d1a17d..6507463455f 100644 --- a/introduction/from_flat_php_to_symfony.rst +++ b/introduction/from_flat_php_to_symfony.rst @@ -681,11 +681,8 @@ migrating the blog from flat PHP to Symfony has improved your life: :doc:`routing `, or rendering :doc:`controllers `; * Symfony gives you **access to open source tools** such as `Doctrine`_ and the - `Templating`_, - :doc:`Security `, - :doc:`Form `, `Validator`_ and - `Translation`_ components (to name - a few); + `Templating`_, :doc:`Security `, :doc:`Form `, + `Validator`_ and `Translation`_ components (to name a few); * The application now enjoys **fully-flexible URLs** thanks to the Routing component; diff --git a/reference/configuration/security.rst b/reference/configuration/security.rst index 5210d782ed3..0036347f776 100644 --- a/reference/configuration/security.rst +++ b/reference/configuration/security.rst @@ -4,7 +4,7 @@ Security Configuration Reference (SecurityBundle) ================================================= -The SecurityBundle integrates the :doc:`Security component ` +The SecurityBundle integrates the :doc:`Security component ` in Symfony applications. All these options are configured under the ``security`` key in your application configuration. diff --git a/security/passwords.rst b/security/passwords.rst index 7f1f5b605b8..6bf37d51806 100644 --- a/security/passwords.rst +++ b/security/passwords.rst @@ -704,10 +704,12 @@ Supported Algorithms * :ref:`sodium ` * :ref:`PBKDF2 ` +* :ref:`Or create a custom password hasher ` + .. TODO missing: - * :ref:`Message Digest ` - * :ref:`Native ` - * :ref:`Plaintext ` +.. * :ref:`Message Digest ` +.. * :ref:`Native ` +.. * :ref:`Plaintext ` .. _reference-security-encoder-auto: @@ -772,6 +774,110 @@ Using the `PBKDF2`_ hasher is no longer recommended since PHP added support for Sodium and BCrypt. Legacy application still using it are encouraged to upgrade to those newer hashing algorithms. +.. _custom-password-hasher: + +Creating a custom Password Hasher +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you need to create your own, it needs to follow these rules: + +#. The class must implement :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface` + (you can also extend :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasher`); + +#. The implementations of + :method:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface::hashPassword` + and :method:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface::isPasswordValid` + **must validate that the password length is no longer than 4096 + characters.** This is for security reasons (see `CVE-2013-5750`_). + + You can use the :method:`Symfony\\Component\\PasswordHasher\\Hasher\\CheckPasswordLengthTrait::isPasswordTooLong` + method for this check. + +.. code-block:: php + + // src/Security/CustomVerySecureHasher.php + namespace App\Security; + + use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; + use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher; + use Symfony\Component\Security\Core\Exception\BadCredentialsException; + + class CustomVerySecureHasher extends UserPasswordHasher + { + use CheckPasswordLengthTrait; + + public function hashPassword(UserInterface $user, string $plainPassword): string + { + if ($this->isPasswordTooLong($user->getPassword())) { + throw new BadCredentialsException('Invalid password.'); + } + + // ... hash the plain password in a secure way + + return $hashedPassword; + } + + public function isPasswordValid(UserInterface $user, string $plainPassword): bool + { + if ($this->isPasswordTooLong($user->getPassword())) { + return false; + } + + // ... validate if the password equals the user's password in a secure way + + return $passwordIsValid; + } + } + +Now, define a password hasher using the ``id`` setting: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + security: + # ... + password_hashers: + app_hasher: + # the service ID of your custom hasher (the FQCN using the default services.yaml) + id: 'App\Security\Hasher\MyCustomPasswordHasher' + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: php + + // config/packages/security.php + use App\Security\Hasher\MyCustomPasswordHasher; + use Symfony\Config\SecurityConfig; + + return static function (SecurityConfig $security) { + // ... + $security->passwordHasher('app_hasher') + // the service ID of your custom hasher (the FQCN using the default services.yaml) + ->id(MyCustomPasswordHasher::class) + ; + }; + .. _`MakerBundle`: https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html .. _`PBKDF2`: https://en.wikipedia.org/wiki/PBKDF2 .. _`libsodium`: https://pecl.php.net/package/libsodium @@ -780,3 +886,4 @@ to those newer hashing algorithms. .. _`cryptographic salt`: https://en.wikipedia.org/wiki/Salt_(cryptography) .. _`the Doctrine docs for information`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/working-with-objects.html#custom-repositories .. _`SymfonyCastsResetPasswordBundle`: https://github.com/symfonycasts/reset-password-bundle +.. _`CVE-2013-5750`: https://symfony.com/blog/cve-2013-5750-security-issue-in-fosuserbundle-login-form diff --git a/security/voters.rst b/security/voters.rst index 3755f9e3665..40d5de58b4e 100644 --- a/security/voters.rst +++ b/security/voters.rst @@ -24,24 +24,15 @@ this could look like, if you want to make a route accessible to the "owner" only In that sense, the following example used throughout this page is a minimal example for voters. -.. tip:: - - Take a look at the - :doc:`authorization ` - article for an even deeper understanding of voters. - -Here's how Symfony works with voters: -All voters are called each time you use the ``isGranted()`` method on Symfony's -authorization checker or call ``denyAccessUnlessGranted()`` in a controller (which -uses the authorization checker), or by -:ref:`access controls `. +Here's how Symfony works with voters: All voters are called each time you +use the ``isGranted()`` method on Symfony's authorization checker or call +``denyAccessUnlessGranted()`` in a controller (which uses the authorization +checker), or by :ref:`access controls `. Ultimately, Symfony takes the responses from all voters and makes the final -decision (to allow or deny access to the resource) according to the strategy defined -in the application, which can be: affirmative, consensus, unanimous or priority. - -For more information take a look at -:ref:`the section about access decision managers `. +decision (to allow or deny access to the resource) according to +:ref:`the strategy defined in the application `, +which can be: affirmative, consensus, unanimous or priority. The Voter Interface -------------------