Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d9a9310

Browse filesBrowse files
committed
Completely re-reading the security book
- more tutorial-styled - tried to move things into other entries - tried to keep as many anchor references as possible
1 parent 153565e commit d9a9310
Copy full SHA for d9a9310
Expand file treeCollapse file tree

20 files changed

+1554
-1706
lines changed

‎book/security.rst

Copy file name to clipboardExpand all lines: book/security.rst
+565-1,703Lines changed: 565 additions & 1703 deletions
Large diffs are not rendered by default.

‎components/map.rst.inc

Copy file name to clipboardExpand all lines: components/map.rst.inc
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
* :doc:`/components/security/firewall`
115115
* :doc:`/components/security/authentication`
116116
* :doc:`/components/security/authorization`
117+
* :doc:`/components/security/secure-tools`
117118

118119
* **Serializer**
119120

‎components/security/index.rst

Copy file name to clipboardExpand all lines: components/security/index.rst
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ Security
88
firewall
99
authentication
1010
authorization
11+
secure-tools

‎components/security/secure-tools.rst

Copy file name to clipboard
+57Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
Securely Comparing Strings and Generating Random Numbers
2+
========================================================
3+
4+
.. versionadded:: 2.2
5+
The ``StringUtils`` and ``SecureRandom`` classes were introduced in Symfony
6+
2.2
7+
8+
The Symfony Security component comes with a collection of nice utilities related
9+
to security. These utilities are used by Symfony, but you should also use
10+
them if you want to solve the problem they address.
11+
12+
Comparing Strings
13+
~~~~~~~~~~~~~~~~~
14+
15+
The time it takes to compare two strings depends on their differences. This
16+
can be used by an attacker when the two strings represent a password for
17+
instance; it is known as a `Timing attack`_.
18+
19+
Internally, when comparing two passwords, Symfony uses a constant-time
20+
algorithm; you can use the same strategy in your own code thanks to the
21+
:class:`Symfony\\Component\\Security\\Core\\Util\\StringUtils` class::
22+
23+
use Symfony\Component\Security\Core\Util\StringUtils;
24+
25+
// is password1 equals to password2?
26+
$bool = StringUtils::equals($password1, $password2);
27+
28+
Generating a secure random Number
29+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30+
31+
Whenever you need to generate a secure random number, you are highly
32+
encouraged to use the Symfony
33+
:class:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom` class::
34+
35+
use Symfony\Component\Security\Core\Util\SecureRandom;
36+
37+
$generator = new SecureRandom();
38+
$random = $generator->nextBytes(10);
39+
40+
The
41+
:method:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom::nextBytes`
42+
methods returns a random string composed of the number of characters passed as
43+
an argument (10 in the above example).
44+
45+
The SecureRandom class works better when OpenSSL is installed but when it's
46+
not available, it falls back to an internal algorithm, which needs a seed file
47+
to work correctly. Just pass a file name to enable it::
48+
49+
$generator = new SecureRandom('/some/path/to/store/the/seed.txt');
50+
$random = $generator->nextBytes(10);
51+
52+
.. note::
53+
54+
If you're using the Symfony Framework, you can access a secure random
55+
instance directly from the container: its name is ``security.secure_random``.
56+
57+
.. _`Timing attack`: http://en.wikipedia.org/wiki/Timing_attack

‎cookbook/map.rst.inc

Copy file name to clipboardExpand all lines: cookbook/map.rst.inc
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138

139139
* :doc:`/cookbook/security/index`
140140

141+
* :doc:`/cookbook/security/form_login_setup`
141142
* :doc:`/cookbook/security/entity_provider`
142143
* :doc:`/cookbook/security/remember_me`
143144
* :doc:`/cookbook/security/impersonating_user`
@@ -153,6 +154,8 @@
153154
* :doc:`/cookbook/security/pre_authenticated`
154155
* :doc:`/cookbook/security/target_path`
155156
* :doc:`/cookbook/security/csrf_in_login_form`
157+
* :doc:`/cookbook/security/access_control`
158+
* :doc:`/cookbook/security/multiple_user_providers`
156159

157160
* **Serializer**
158161

‎cookbook/security/access_control.rst

Copy file name to clipboard
+299Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
How does the Security access_control Work?
2+
==========================================
3+
4+
For each incoming request, Symfony checks each ``access_control`` entry
5+
to find *one* that matches the current request. As soon as it finds a matching
6+
``access_control`` entry, it stops - only the **first** matching ``access_control``
7+
is used to enforce access.
8+
9+
Each ``access_control`` has several options that configure two different
10+
things:
11+
12+
#. :ref:`should the incoming request match this access control entry <security-book-access-control-matching-options>`
13+
#. :ref:`once it matches, should some sort of access restriction be enforced <security-book-access-control-enforcement-options>`:
14+
15+
.. _security-book-access-control-matching-options:
16+
17+
1. Matching Options
18+
-------------------
19+
20+
Symfony creates an instance of :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher`
21+
for each ``access_control`` entry, which determines whether or not a given
22+
access control should be used on this request. The following ``access_control``
23+
options are used for matching:
24+
25+
* ``path``
26+
* ``ip`` or ``ips``
27+
* ``host``
28+
* ``methods``
29+
30+
Take the following ``access_control`` entries as an example:
31+
32+
.. configuration-block::
33+
34+
.. code-block:: yaml
35+
36+
# app/config/security.yml
37+
security:
38+
# ...
39+
access_control:
40+
- { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 }
41+
- { path: ^/admin, roles: ROLE_USER_HOST, host: symfony\.com$ }
42+
- { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] }
43+
- { path: ^/admin, roles: ROLE_USER }
44+
45+
.. code-block:: xml
46+
47+
<!-- app/config/security.xml -->
48+
<?xml version="1.0" encoding="UTF-8"?>
49+
<srv:container xmlns="http://symfony.com/schema/dic/security"
50+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
51+
xmlns:srv="http://symfony.com/schema/dic/services"
52+
xsi:schemaLocation="http://symfony.com/schema/dic/services
53+
http://symfony.com/schema/dic/services/services-1.0.xsd">
54+
55+
<config>
56+
<!-- ... -->
57+
<access-control>
58+
<rule path="^/admin" role="ROLE_USER_IP" ip="127.0.0.1" />
59+
<rule path="^/admin" role="ROLE_USER_HOST" host="symfony\.com$" />
60+
<rule path="^/admin" role="ROLE_USER_METHOD" method="POST, PUT" />
61+
<rule path="^/admin" role="ROLE_USER" />
62+
</access-control>
63+
</config>
64+
</srv:container>
65+
66+
.. code-block:: php
67+
68+
// app/config/security.php
69+
$container->loadFromExtension('security', array(
70+
// ...
71+
'access_control' => array(
72+
array(
73+
'path' => '^/admin',
74+
'role' => 'ROLE_USER_IP',
75+
'ip' => '127.0.0.1',
76+
),
77+
array(
78+
'path' => '^/admin',
79+
'role' => 'ROLE_USER_HOST',
80+
'host' => 'symfony\.com$',
81+
),
82+
array(
83+
'path' => '^/admin',
84+
'role' => 'ROLE_USER_METHOD',
85+
'method' => 'POST, PUT',
86+
),
87+
array(
88+
'path' => '^/admin',
89+
'role' => 'ROLE_USER',
90+
),
91+
),
92+
));
93+
94+
For each incoming request, Symfony will decide which ``access_control``
95+
to use based on the URI, the client's IP address, the incoming host name,
96+
and the request method. Remember, the first rule that matches is used, and
97+
if ``ip``, ``host`` or ``method`` are not specified for an entry, that ``access_control``
98+
will match any ``ip``, ``host`` or ``method``:
99+
100+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
101+
| URI | IP | HOST | METHOD | ``access_control`` | Why? |
102+
+=================+=============+=============+============+================================+=============================================================+
103+
| ``/admin/user`` | 127.0.0.1 | example.com | GET | rule #1 (``ROLE_USER_IP``) | The URI matches ``path`` and the IP matches ``ip``. |
104+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
105+
| ``/admin/user`` | 127.0.0.1 | symfony.com | GET | rule #1 (``ROLE_USER_IP``) | The ``path`` and ``ip`` still match. This would also match |
106+
| | | | | | the ``ROLE_USER_HOST`` entry, but *only* the **first** |
107+
| | | | | | ``access_control`` match is used. |
108+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
109+
| ``/admin/user`` | 168.0.0.1 | symfony.com | GET | rule #2 (``ROLE_USER_HOST``) | The ``ip`` doesn't match the first rule, so the second |
110+
| | | | | | rule (which matches) is used. |
111+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
112+
| ``/admin/user`` | 168.0.0.1 | symfony.com | POST | rule #2 (``ROLE_USER_HOST``) | The second rule still matches. This would also match the |
113+
| | | | | | third rule (``ROLE_USER_METHOD``), but only the **first** |
114+
| | | | | | matched ``access_control`` is used. |
115+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
116+
| ``/admin/user`` | 168.0.0.1 | example.com | POST | rule #3 (``ROLE_USER_METHOD``) | The ``ip`` and ``host`` don't match the first two entries, |
117+
| | | | | | but the third - ``ROLE_USER_METHOD`` - matches and is used. |
118+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
119+
| ``/admin/user`` | 168.0.0.1 | example.com | GET | rule #4 (``ROLE_USER``) | The ``ip``, ``host`` and ``method`` prevent the first |
120+
| | | | | | three entries from matching. But since the URI matches the |
121+
| | | | | | ``path`` pattern of the ``ROLE_USER`` entry, it is used. |
122+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
123+
| ``/foo`` | 127.0.0.1 | symfony.com | POST | matches no entries | This doesn't match any ``access_control`` rules, since its |
124+
| | | | | | URI doesn't match any of the ``path`` values. |
125+
+-----------------+-------------+-------------+------------+--------------------------------+-------------------------------------------------------------+
126+
127+
.. _security-book-access-control-enforcement-options:
128+
129+
2. Access Enforcement
130+
---------------------
131+
132+
Once Symfony has decided which ``access_control`` entry matches (if any),
133+
it then *enforces* access restrictions based on the ``roles`` and ``requires_channel``
134+
options:
135+
136+
* ``role`` If the user does not have the given role(s), then access is denied
137+
(internally, an :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`
138+
is thrown);
139+
140+
* ``requires_channel`` If the incoming request's channel (e.g. ``http``)
141+
does not match this value (e.g. ``https``), the user will be redirected
142+
(e.g. redirected from ``http`` to ``https``, or vice versa).
143+
144+
.. tip::
145+
146+
If access is denied, the system will try to authenticate the user if not
147+
already (e.g. redirect the user to the login page). If the user is already
148+
logged in, the 403 "access denied" error page will be shown. See
149+
:doc:`/cookbook/controller/error_pages` for more information.
150+
151+
.. _book-security-securing-ip:
152+
153+
Securing by IP
154+
--------------
155+
156+
Certain situations may arise when you may need to restrict access to a given
157+
path based on IP. This is particularly relevant in the case of
158+
:ref:`Edge Side Includes <edge-side-includes>` (ESI), for example. When ESI is
159+
enabled, it's recommended to secure access to ESI URLs. Indeed, some ESI may
160+
contain some private content like the current logged in user's information. To
161+
prevent any direct access to these resources from a web browser (by guessing the
162+
ESI URL pattern), the ESI route **must** be secured to be only visible from
163+
the trusted reverse proxy cache.
164+
165+
.. versionadded:: 2.3
166+
Version 2.3 allows multiple IP addresses in a single rule with the ``ips: [a, b]``
167+
construct. Prior to 2.3, users should create one rule per IP address to match and
168+
use the ``ip`` key instead of ``ips``.
169+
170+
.. caution::
171+
172+
As you'll read in the explanation below the example, the ``ip`` option
173+
does not restrict to a specific IP address. Instead, using the ``ip``
174+
key means that the ``access_control`` entry will only match this IP address,
175+
and users accessing it from a different IP address will continue down
176+
the ``access_control`` list.
177+
178+
Here is an example of how you might secure all ESI routes that start with a
179+
given prefix, ``/esi``, from outside access:
180+
181+
.. configuration-block::
182+
183+
.. code-block:: yaml
184+
185+
# app/config/security.yml
186+
security:
187+
# ...
188+
access_control:
189+
- { path: ^/esi, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1] }
190+
- { path: ^/esi, roles: ROLE_NO_ACCESS }
191+
192+
.. code-block:: xml
193+
194+
<!-- app/config/security.xml -->
195+
<?xml version="1.0" encoding="UTF-8"?>
196+
<srv:container xmlns="http://symfony.com/schema/dic/security"
197+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
198+
xmlns:srv="http://symfony.com/schema/dic/services"
199+
xsi:schemaLocation="http://symfony.com/schema/dic/services
200+
http://symfony.com/schema/dic/services/services-1.0.xsd">
201+
202+
<config>
203+
<!-- ... -->
204+
<access-control>
205+
<rule path="^/esi" role="IS_AUTHENTICATED_ANONYMOUSLY"
206+
ips="127.0.0.1, ::1" />
207+
<rule path="^/esi" role="ROLE_NO_ACCESS" />
208+
</access-control>
209+
</config>
210+
</srv:container>
211+
212+
.. code-block:: php
213+
214+
// app/config/security.php
215+
$container->loadFromExtension('security', array(
216+
// ...
217+
'access_control' => array(
218+
array(
219+
'path' => '^/esi',
220+
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
221+
'ips' => '127.0.0.1, ::1'
222+
),
223+
array(
224+
'path' => '^/esi',
225+
'role' => 'ROLE_NO_ACCESS'
226+
),
227+
),
228+
));
229+
230+
Here is how it works when the path is ``/esi/something`` coming from the
231+
``10.0.0.1`` IP:
232+
233+
* The first access control rule is ignored as the ``path`` matches but the
234+
``ip`` does not match either of the IPs listed;
235+
236+
* The second access control rule is enabled (the only restriction being the
237+
``path`` and it matches): as the user cannot have the ``ROLE_NO_ACCESS``
238+
role as it's not defined, access is denied (the ``ROLE_NO_ACCESS`` role can
239+
be anything that does not match an existing role, it just serves as a trick
240+
to always deny access).
241+
242+
Now, if the same request comes from ``127.0.0.1`` or ``::1`` (the IPv6 loopback
243+
address):
244+
245+
* Now, the first access control rule is enabled as both the ``path`` and the
246+
``ip`` match: access is allowed as the user always has the
247+
``IS_AUTHENTICATED_ANONYMOUSLY`` role.
248+
249+
* The second access rule is not examined as the first rule matched.
250+
251+
.. _book-security-securing-channel:
252+
253+
Forcing a Channel (http, https)
254+
-------------------------------
255+
256+
You can also require a user to access a URL via SSL; just use the
257+
``requires_channel`` argument in any ``access_control`` entries. If this
258+
``access_control`` is matched and the request is using the ``http`` channel,
259+
the user will be redirected to ``https``:
260+
261+
.. configuration-block::
262+
263+
.. code-block:: yaml
264+
265+
# app/config/security.yml
266+
security:
267+
# ...
268+
access_control:
269+
- { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
270+
271+
.. code-block:: xml
272+
273+
<!-- app/config/security.xml -->
274+
<?xml version="1.0" encoding="UTF-8"?>
275+
<srv:container xmlns="http://symfony.com/schema/dic/security"
276+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
277+
xmlns:srv="http://symfony.com/schema/dic/services"
278+
xsi:schemaLocation="http://symfony.com/schema/dic/services
279+
http://symfony.com/schema/dic/services/services-1.0.xsd">
280+
281+
<access-control>
282+
<rule path="^/cart/checkout"
283+
role="IS_AUTHENTICATED_ANONYMOUSLY"
284+
requires-channel="https" />
285+
</access-control>
286+
</srv:container>
287+
288+
.. code-block:: php
289+
290+
// app/config/security.php
291+
$container->loadFromExtension('security', array(
292+
'access_control' => array(
293+
array(
294+
'path' => '^/cart/checkout',
295+
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
296+
'requires_channel' => 'https',
297+
),
298+
),
299+
));

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.