diff --git a/book/security.rst b/book/security.rst
index 2e7409bdb8a..59432b994d9 100644
--- a/book/security.rst
+++ b/book/security.rst
@@ -660,7 +660,7 @@ see :doc:`/cookbook/security/form_login`.
),
),
- **3. Be sure ``/login_check`` is behind a firewall**
+ **3. Be sure /login_check is behind a firewall**
Next, make sure that your ``check_path`` URL (e.g. ``/login_check``)
is behind the firewall you're using for your form login (in this example,
@@ -1206,19 +1206,6 @@ custom user class is that it implements the :class:`Symfony\\Component\\Security
interface. This means that your concept of a "user" can be anything, as long
as it implements this interface.
-.. versionadded:: 2.1
- In Symfony 2.1, the ``equals`` method was removed from ``UserInterface``.
- If you need to override the default implementation of comparison logic,
- implement the new :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`
- interface.
-
-.. note::
-
- The user object will be serialized and saved in the session during requests,
- therefore it is recommended that you `implement the \Serializable interface`_
- in your user object. This is especially important if your ``User`` class
- has a parent class with private properties.
-
Next, configure an ``entity`` user provider, and point it to your ``User``
class:
@@ -1278,7 +1265,7 @@ in plain text (whether those users are stored in a configuration file or in
a database somewhere). Of course, in a real application, you'll want to encode
your users' passwords for security reasons. This is easily accomplished by
mapping your User class to one of several built-in "encoders". For example,
-to store your users in memory, but obscure their passwords via ``sha1``,
+to store your users in memory, but obscure their passwords via ``bcrypt``,
do the following:
.. configuration-block::
@@ -1292,14 +1279,17 @@ do the following:
in_memory:
memory:
users:
- ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' }
- admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' }
+ ryan:
+ password: $2a$12$w/aHvnC/XNeDVrrl65b3dept8QcKqpADxUlbraVXXsC03Jam5hvoO
+ roles: 'ROLE_USER'
+ admin:
+ password: $2a$12$HmOsqRDJK0HuMDQ5Fb2.AOLMQHyNHGD0seyjU3lEVusjT72QQEIpW
+ roles: 'ROLE_ADMIN'
encoders:
Symfony\Component\Security\Core\User\User:
- algorithm: sha1
- iterations: 1
- encode_as_base64: false
+ algorithm: bcrypt
+ cost: 12
.. code-block:: xml
@@ -1309,18 +1299,18 @@ do the following:
+ algorithm="bcrypt"
+ cost="12"
+ />
.. code-block:: php
@@ -1333,11 +1323,11 @@ do the following:
'memory' => array(
'users' => array(
'ryan' => array(
- 'password' => 'bb87a29949f3a1ee0559f8a57357487151281386',
+ 'password' => '$2a$12$w/aHvnC/XNeDVrrl65b3dept8QcKqpADxUlbraVXXsC03Jam5hvoO',
'roles' => 'ROLE_USER',
),
'admin' => array(
- 'password' => '74913f5cd5f61ec0bcfdb775414c2fb3d161b620',
+ 'password' => '$2a$12$HmOsqRDJK0HuMDQ5Fb2.AOLMQHyNHGD0seyjU3lEVusjT72QQEIpW',
'roles' => 'ROLE_ADMIN',
),
),
@@ -1346,77 +1336,36 @@ do the following:
),
'encoders' => array(
'Symfony\Component\Security\Core\User\User' => array(
- 'algorithm' => 'sha1',
- 'iterations' => 1,
- 'encode_as_base64' => false,
+ 'algorithm' => 'bcrypt',
+ 'iterations' => 12,
),
),
));
-By setting the ``iterations`` to ``1`` and the ``encode_as_base64`` to false,
-the password is simply run through the ``sha1`` algorithm one time and without
-any extra encoding. You can now calculate the hashed password either programmatically
-(e.g. ``hash('sha1', 'ryanpass')``) or via some online tool like `functions-online.com`_
-
-.. tip::
-
- Supported algorithms for this method depend on your PHP version.
- A full list is available calling the PHP function :phpfunction:`hash_algos`.
-
-If you're creating your users dynamically (and storing them in a database),
-you can use even tougher hashing algorithms and then rely on an actual password
-encoder object to help you encode passwords. For example, suppose your User
-object is ``Acme\UserBundle\Entity\User`` (like in the above example). First,
-configure the encoder for that user:
-
-.. configuration-block::
-
- .. code-block:: yaml
-
- # app/config/security.yml
- security:
- # ...
-
- encoders:
- Acme\UserBundle\Entity\User: sha512
-
- .. code-block:: xml
-
-
-
-
-
-
-
+.. versionadded:: 2.2
+ The BCrypt encoder was introduced in Symfony 2.2.
- .. code-block:: php
+You can now calculate the hashed password either programmatically
+(e.g. ``password_hash('ryanpass', PASSWORD_BCRYPT, array('cost' => 12));``)
+or via some online tool.
- // app/config/security.php
- $container->loadFromExtension('security', array(
- // ...
- 'encoders' => array(
- 'Acme\UserBundle\Entity\User' => 'sha512',
- ),
- ));
+.. include:: /cookbook/security/_ircmaxwell_password-compat.rst.inc
-In this case, you're using the stronger ``sha512`` algorithm. Also, since
-you've simply specified the algorithm (``sha512``) as a string, the system
-will default to hashing your password 5000 times in a row and then encoding
-it as base64. In other words, the password has been greatly obfuscated so
-that the hashed password can't be decoded (i.e. you can't determine the password
-from the hashed password).
+Supported algorithms for this method depend on your PHP version. A full list
+is available by calling the PHP function :phpfunction:`hash_algos`.
.. versionadded:: 2.2
As of Symfony 2.2 you can also use the :ref:`PBKDF2 `
- and :ref:`BCrypt ` password encoders.
+ password encoder.
Determining the Hashed Password
...............................
-If you have some sort of registration form for users, you'll need to be able
-to determine the hashed password so that you can set it on your user. No
-matter what algorithm you configure for your user object, the hashed password
-can always be determined in the following way from a controller::
+If you're storing users in the database and you have some sort of registration
+form for users, you'll need to be able to determine the hashed password so
+that you can set it on your user before inserting it. No matter what algorithm
+you configure for your user object, the hashed password can always be determined
+in the following way from a controller::
$factory = $this->get('security.encoder_factory');
$user = new Acme\UserBundle\Entity\User();
@@ -1425,6 +1374,10 @@ can always be determined in the following way from a controller::
$password = $encoder->encodePassword('ryanpass', $user->getSalt());
$user->setPassword($password);
+In order for this to work, just make sure that you have the encoder for your
+user class (e.g. ``Acme\UserBundle\Entity\User``) configured under the ``encoders``
+key in ``app/config/security.yml``.
+
.. caution::
When you allow a user to submit a plaintext password (e.g. registration
@@ -2077,5 +2030,4 @@ Learn more from the Cookbook
.. _`JMSSecurityExtraBundle`: http://jmsyst.com/bundles/JMSSecurityExtraBundle/1.2
.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
.. _`implement the \Serializable interface`: http://php.net/manual/en/class.serializable.php
-.. _`functions-online.com`: http://www.functions-online.com/sha1.html
.. _`Timing attack`: http://en.wikipedia.org/wiki/Timing_attack
diff --git a/cookbook/security/_ircmaxwell_password-compat.rst.inc b/cookbook/security/_ircmaxwell_password-compat.rst.inc
new file mode 100644
index 00000000000..3f96c454488
--- /dev/null
+++ b/cookbook/security/_ircmaxwell_password-compat.rst.inc
@@ -0,0 +1,13 @@
+.. caution::
+
+ If you're using PHP 5.4 or lower, you'll need to install the ``ircmaxell/password-compat``
+ library via Composer in order to be able to use the ``bcrypt`` encoder:
+
+ .. code-block:: json
+
+ {
+ "require": {
+ ...
+ "ircmaxell/password-compat": "~1.0.3"
+ }
+ }
diff --git a/cookbook/security/entity_provider.rst b/cookbook/security/entity_provider.rst
index b412c878c9f..b6919839b83 100644
--- a/cookbook/security/entity_provider.rst
+++ b/cookbook/security/entity_provider.rst
@@ -32,7 +32,7 @@ The Data Model
--------------
For the purpose of this cookbook, the ``AcmeUserBundle`` bundle contains a
-``User`` entity class with the following fields: ``id``, ``username``, ``salt``,
+``User`` entity class with the following fields: ``id``, ``username``,
``password``, ``email`` and ``isActive``. The ``isActive`` field tells whether
or not the user account is active.
@@ -77,11 +77,6 @@ focus on the most important methods that come from the
*/
private $username;
- /**
- * @ORM\Column(type="string", length=32)
- */
- private $salt;
-
/**
* @ORM\Column(type="string", length=64)
*/
@@ -100,7 +95,8 @@ focus on the most important methods that come from the
public function __construct()
{
$this->isActive = true;
- $this->salt = md5(uniqid(null, true));
+ // may not be needed, see section on salt below
+ // $this->salt = md5(uniqid(null, true));
}
/**
@@ -116,7 +112,9 @@ focus on the most important methods that come from the
*/
public function getSalt()
{
- return $this->salt;
+ // you *may* need a real salt depending on your encoder
+ // see section on salt below
+ return null;
}
/**
@@ -150,8 +148,9 @@ focus on the most important methods that come from the
return serialize(array(
$this->id,
$this->username,
- $this->salt,
$this->password,
+ // see section on salt below
+ // $this->salt,
));
}
@@ -163,19 +162,13 @@ focus on the most important methods that come from the
list (
$this->id,
$this->username,
- $this->salt,
$this->password,
+ // see section on salt below
+ // $this->salt
) = unserialize($serialized);
}
}
-.. note::
-
- If you choose to implement
- :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`,
- you determine yourself which properties need to be compared to distinguish
- your user objects.
-
.. tip::
:ref:`Generate the database table `
@@ -198,27 +191,9 @@ interface forces the class to implement the five following methods:
For more details on each of these, see :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`.
-.. sidebar:: What is the importance of serialize and unserialize?
-
- The :phpclass:`Serializable` interface and its ``serialize`` and ``unserialize``
- methods have been added to allow the ``User`` class to be serialized
- to the session. This may or may not be needed depending on your setup,
- but it's probably a good idea. The ``id`` is the most important value
- that needs to be serialized because the
- :method:`Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider::refreshUser`
- method reloads the user on each request by using the ``id``. In practice,
- this means that the User object is reloaded from the database on each
- request using the ``id`` from the serialized object. This makes sure
- all of the User's data is fresh.
-
- Symfony also uses the ``username``, ``salt``, and ``password`` to verify
- that the User has not changed between requests. Failing to serialize
- these may cause you to be logged out on each request. If your User implements
- :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`,
- then instead of these properties being checked, your ``isEqualTo`` method
- is simply called, and you can check whatever properties you want. Unless
- you understand this, you probably *won't* need to implement this interface
- or worry about it.
+If you're curious about the ``serialize`` method or are looking for details
+on the logic of seeing if the User stored in the session is the same as the
+one stored in the database, see :ref:`cookbook-security-serialize-equatable`.
Below is an export of the ``User`` table from MySQL with user ``admin`` and
password ``admin`` (which has been encoded). For details on how to create
@@ -227,16 +202,30 @@ user records and encode their password, see :ref:`book-security-encoding-user-pa
.. code-block:: bash
$ mysql> select * from acme_users;
- +----+----------+------+------------------------------------------+--------------------+-----------+
- | id | username | salt | password | email | is_active |
- +----+----------+------+------------------------------------------+--------------------+-----------+
- | 1 | admin | | d033e22ae348aeb5660fc2140aec35850c4da997 | admin@example.com | 1 |
- +----+----------+------+------------------------------------------+--------------------+-----------+
+ +----+----------+------------------------------------------+--------------------+-----------+
+ | id | username | password | email | is_active |
+ +----+----------+------------------------------------------+--------------------+-----------+
+ | 1 | admin | d033e22ae348aeb5660fc2140aec35850c4da997 | admin@example.com | 1 |
+ +----+----------+------------------------------------------+--------------------+-----------+
The next part will focus on how to authenticate one of these users
thanks to the Doctrine entity user provider and a couple of lines of
configuration.
+.. sidebar:: Do you need to use a Salt?
+
+ Yes. Hashing a password with a salt is a necessary step so that encoded
+ passwords can't be decoded. However, some encoders - like Bcrypt - have
+ a built-in salt mechanism. If you configure ``bcrypt`` as your encoder
+ in ``security.yml`` (see the next section), then ``getSalt()`` should
+ return ``null``, so that Bcrypt generates the salt itself.
+
+ However, if you use an encoder that does *not* have a built-in salting
+ ability (e.g. ``sha512``), you *must* (from a security perspective) generate
+ your own, random salt, store it on a ``salt`` property that is saved to
+ the database, and return it from ``getSalt()``. Some of the code needed
+ is commented out in the above example.
+
Authenticating Someone against a Database
-----------------------------------------
@@ -257,9 +246,7 @@ then be checked against your User entity records in the database:
security:
encoders:
Acme\UserBundle\Entity\User:
- algorithm: sha1
- encode_as_base64: false
- iterations: 1
+ algorithm: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
@@ -282,9 +269,7 @@ then be checked against your User entity records in the database:
ROLE_USER
@@ -307,9 +292,7 @@ then be checked against your User entity records in the database:
$container->loadFromExtension('security', array(
'encoders' => array(
'Acme\UserBundle\Entity\User' => array(
- 'algorithm' => 'sha1',
- 'encode_as_base64' => false,
- 'iterations' => 1,
+ 'algorithm' => 'bcrypt',
),
),
'role_hierarchy' => array(
@@ -335,12 +318,14 @@ then be checked against your User entity records in the database:
),
));
-The ``encoders`` section associates the ``sha1`` password encoder to the entity
+The ``encoders`` section associates the ``bcrypt`` password encoder to the entity
class. This means that Symfony will expect the password that's stored in
-the database to be encoded using this algorithm. For details on how to create
+the database to be encoded using this encoder. For details on how to create
a new User object with a properly encoded password, see the
:ref:`book-security-encoding-user-password` section of the security chapter.
+.. include:: /cookbook/security/_ircmaxwell_password-compat.rst.inc
+
The ``providers`` section defines an ``administrators`` user provider. A
user provider is a "source" of where users are loaded during authentication.
In this case, the ``entity`` keyword means that Symfony will use the Doctrine
@@ -749,3 +734,45 @@ fetch the user and their associated roles with a single query::
The ``QueryBuilder::leftJoin()`` method joins and fetches related roles from
the ``AcmeUserBundle:User`` model class when a user is retrieved by their email
address or username.
+
+.. _`cookbook-security-serialize-equatable`:
+
+Understanding serialize and how a User is Saved in the Session
+--------------------------------------------------------------
+
+If you're curious about the importance of the ``serialize()`` method inside
+the ``User`` class or how the User object is serialized or deserialized, then
+this section is for you. If not, feel free to skip this.
+
+Once the user is logged in, the entire User object is serialized into the
+session. On the next request, the User object is deserialized. Then, value
+of the ``id`` property is used to re-query for a fresh User object from the
+database. Finally, the fresh User object is compared in some way to the deserialized
+User object to make sure that they represent the same user. For example, if
+the ``username`` on the 2 User objects doesn't match for some reason, then
+the user will be logged out for security reasons.
+
+Even though this all happens automatically, there are a few important side-effects.
+
+First, the :phpclass:`Serializable` interface and its ``serialize`` and ``unserialize``
+methods have been added to allow the ``User`` class to be serialized
+to the session. This may or may not be needed depending on your setup,
+but it's probably a good idea. In theory, only the ``id`` needs to be serialized,
+because the :method:`Symfony\\Bridge\\Doctrine\\Security\\User\\EntityUserProvider::refreshUser`
+method refreshes the user on each request by using the ``id`` (as explained
+above). However in practice, this means that the User object is reloaded from
+the database on each request using the ``id`` from the serialized object.
+This makes sure all of the User's data is fresh.
+
+Symfony also uses the ``username``, ``salt``, and ``password`` to verify
+that the User has not changed between requests. Failing to serialize
+these may cause you to be logged out on each request. If your User implements
+the :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`,
+then instead of these properties being checked, your ``isEqualTo`` method
+is simply called, and you can check whatever properties you want. Unless
+you understand this, you probably *won't* need to implement this interface
+or worry about it.
+
+.. versionadded:: 2.1
+ In Symfony 2.1, the ``equals`` method was removed from ``UserInterface``
+ and the ``EquatableInterface`` was added in its place.