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
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
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);
}
}
注釈
A token can be of any class, as long as it implements
TokenInterface
.
The Authentication Manager¶
The default authentication manager is an instance of
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.
注釈
You may write your own authentication manager, the only requirement is that
it implements AuthenticationManagerInterface
.
Authentication Providers¶
Each provider (since it implements
AuthenticationProviderInterface
)
has a 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 authenticate()
method.
This method should return an authenticated token or throw an
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 DaoAuthenticationProvider
.
It fetches the user’s data from a UserProviderInterface
,
uses a PasswordEncoderInterface
to create a hash of the password and returns an authenticated token if the
password was valid:
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\UserChecker;
$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 encoders (see below)
$encoderFactory = new EncoderFactory(...);
$daoProvider = new DaoAuthenticationProvider(
$userProvider,
$userChecker,
'secured_area',
$encoderFactory
);
$daoProvider->authenticate($unauthenticatedToken);
注釈
The example above demonstrates the use of the “in-memory” user provider,
but you may use any user provider, as long as it implements
UserProviderInterface
.
It is also possible to let multiple user providers try to find the user’s
data, using the ChainUserProvider
.
The Password Encoder Factory¶
The DaoAuthenticationProvider
uses an encoder factory to create a password encoder for a given type of
user. This allows you to use different encoding strategies for different
types of users. The default EncoderFactory
receives an array of encoders:
use Acme\Entity\LegacyUser;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\Security\Core\User\User;
$defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
$weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1);
$encoders = [
User::class => $defaultEncoder,
LegacyUser::class => $weakEncoder,
// ...
];
$encoderFactory = new EncoderFactory($encoders);
Each encoder should implement PasswordEncoderInterface
or be an array with a class
and an arguments
key, which allows the
encoder factory to construct the encoder only when it is needed.
Creating a custom Password Encoder¶
There are many built-in password encoders. But if you need to create your own, it needs to follow these rules:
The class must implement
PasswordEncoderInterface
;The implementations of
encodePassword()
andisPasswordValid()
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 theisPasswordTooLong()
method for this check:use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder; use Symfony\Component\Security\Core\Exception\BadCredentialsException; class FoobarEncoder extends BasePasswordEncoder { public function encodePassword($raw, $salt) { if ($this->isPasswordTooLong($raw)) { throw new BadCredentialsException('Invalid password.'); } // ... } public function isPasswordValid($encoded, $raw, $salt) { if ($this->isPasswordTooLong($raw)) { return false; } // ... } }
Using Password Encoders¶
When the getEncoder()
method of the password encoder factory is called with the user object as
its first argument, it will return an encoder of type PasswordEncoderInterface
which should be used to encode this user’s password:
// a Acme\Entity\LegacyUser instance
$user = ...;
// the password that was submitted, e.g. when registering
$plainPassword = ...;
$encoder = $encoderFactory->getEncoder($user);
// returns $weakEncoder (see above)
$encodedPassword = $encoder->encodePassword($plainPassword, $user->getSalt());
$user->setPassword($encodedPassword);
// ... 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 = $encoder->isPasswordValid(
$user->getPassword(), // the encoded password
$plainPassword, // the submitted password
$user->getSalt()
);
Authentication Events¶
The security component provides the following authentication events:
Name | Event Constant | Argument Passed to the Listener |
---|---|---|
security.authentication.success | AuthenticationEvents::AUTHENTICATION_SUCCESS |
AuthenticationEvent |
security.authentication.failure | AuthenticationEvents::AUTHENTICATION_FAILURE |
AuthenticationFailureEvent |
security.interactive_login | SecurityEvents::INTERACTIVE_LOGIN |
InteractiveLoginEvent |
security.switch_user | SecurityEvents::SWITCH_USER |
SwitchUserEvent |
security.logout_on_change | Symfony\Component\Security\Http\Event\DeauthenticatedEvent |
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 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 doing some clean-up task when a logout has been triggered.
参考
For more information on switching users, see How to Impersonate a User.