8000 feature #4606 Completely re-reading the security book (weaverryan) · symfony/symfony-docs@00a13d6 · GitHub
[go: up one dir, main page]

Skip to content
  • Pricing
  • 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 00a13d6

    Browse files
    committed
    feature #4606 Completely re-reading the security book (weaverryan)
    This PR was merged into the 2.3 branch. Discussion ---------- Completely re-reading the security book | Q | A | ------------- | --- | Doc fix? | no | New docs? | no | Applies to | all | Fixed tickets | n/a Well, this should be interesting :). Several years ago, I bootstrapped the security chapter and it's been there ever since. That fact doesn't necessarily mean that it was good. I've just re-read and basically re-written the chapter from scratch. I thought it was too long, too theoretical in the beginning, and it also had some extra "baggage" just from being old and having things added to it. My goal is to: A) Not actually remove anything of importance - I've done my best with this B) Actually get feedback that this is better. I feel that this is better, but rewrites aren't automatically better. It's like the second album of a band - even though they're older and wiser, maybe the original is still better :). I hope not! Todo: - [ ] fill in config blocks - @xabbuh if you happen to have some time and can help, I would be even more in debt to you :) As I merge to 2.5 and up, I'll need to check for the `versionadded` tags on each branch and re-add those things to the new chapter. Thanks! Commits ------- fe9fdac [#4606] Getting my XML (and PHP) on in the new security chapter aedfcd2 [#4606] Tweaks thanks entirely to stof 614da15 Changing to _ for consistency 95d6a7d [#4606] Updating thanks to comments from everyone! d9a9310 Completely re-reading the security book
    2 parents 153565e + fe9fdac commit 00a13d6

    21 files changed

    +1663
    -1512
    lines changed

    book/security.rst

    Lines changed: 676 additions & 1508 deletions
    Large diffs are not rendered by default.

    components/map.rst.inc

    Lines 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

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

    cookbook/map.rst.inc

    Lines 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_provide 6284 r`
    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

    Lines changed: 291 additions & 0 deletions
    < 436E /tr>
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,291 @@
    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+
    Matching access_control By IP
    154+
    -----------------------------
    155+
    156+
    Certain situations may arise when you need to have an ``access_control``
    157+
    entry that *only* matches requests coming from some IP address or range.
    158+
    For example, this *could* be used to deny access to a URL pattern to all
    159+
    requests *except* those from a trusted, internal server.
    160+
    161+
    .. caution::
    162+
    163+
    As you'll read in the explanation below the example, the ``ips`` option
    164+
    does not restrict to a specific IP address. Instead, using the ``ips``
    165+
    key means that the ``access_control`` entry will only match this IP address,
    166+
    and users accessing it from a different IP address will continue down
    167+
    the ``access_control`` list.
    168+
    169+
    Here is an example of how you configure some example ``/internal*`` URL
    170+
    pattern so that it is only accessible by requests from the local server itself:
    171+
    172+
    .. configuration-block::
    173+
    174+
    .. code-block:: yaml
    175+
    176+
    # app/config/security.yml
    177+
    security:
    178+
    # ...
    179+
    access_control:
    180+
    #
    181+
    - { path: ^/internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1] }
    182+
    - { path: ^/internal, roles: ROLE_NO_ACCESS }
    183+
    184+
    .. code-block:: xml
    185+
    186+
    <!-- app/config/security.xml -->
    187+
    <?xml version="1.0" encoding="UTF-8"?>
    188+
    <srv:container xmlns="http://symfony.com/schema/dic/security"
    189+
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    190+
    xmlns:srv="http://symfony.com/schema/dic/services"
    191+
    xsi:schemaLocation="http://symfony.com/schema/dic/services
    192+
    http://symfony.com/schema/dic/services/services-1.0.xsd">
    193+
    194+
    <config>
    195+
    <!-- ... -->
    196+
    <access-control>
    197+
    <rule path="^/esi" role="IS_AUTHENTICATED_ANONYMOUSLY"
    198+
    ips="127.0.0.1, ::1" />
    199+
    <rule path="^/esi" role="ROLE_NO_ACCESS" />
    200+
    </access-control>
    201+
    </config>
    202+
    </srv:container>
    203+
    204+
    .. code-block:: php
    205+
    206+
    // app/config/security.php
    207+
    $container->loadFromExtension('security', array(
    208+
    // ...
    209+
    'access_control' => array(
    210+
    array(
    211+
    'path' => '^/esi',
    212+
    'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
    213+
    'ips' => '127.0.0.1, ::1'
    214+
    ),
    215+
    array(
    216+
    'path' => '^/esi',
    217+
    'role' => 'ROLE_NO_ACCESS'
    218+
    ),
    219+
    ),
    220+
    ));
    221+
    222+
    Here is how it works when the path is ``/internal/something`` coming from
    223+
    the external IP address ``10.0.0.1``:
    224+
    225+
    * The first access control rule is ignored as the ``path`` matches but the
    226+
    IP address does not match either of the IPs listed;
    227+
    228+
    * The second access control rule is enabled (the only restriction being the
    229+
    ``path``) and so it matches. If you make sure that no users ever have
    230+
    ``ROLE_NO_ACCESS``, then access is denied (``ROLE_NO_ACCESS`` can be anything
    231+
    that does not match an existing role, it just serves as a trick to always
    232+
    deny access).
    233+
    234+
    But if the same request comes from ``127.0.0.1`` or ``::1`` (the IPv6 loopback
    235+
    address):
    236+
    237+
    * Now, the first access control rule is enabled as both the ``path`` and the
    238+
    ``ip`` match: access is allowed as the user always has the
    239+
    ``IS_AUTHENTICATED_ANONYMOUSLY`` role.
    240+
    241+
    * The second access rule is not examined as the first rule matched.
    242+
    243+
    .. _book-security-securing-channel:
    244+
    245+
    Forcing a Channel (http, https)
    246+
    -------------------------------
    247+
    248+
    You can also require a user to access a URL via SSL; just use the
    249+
    ``requires_channel`` argument in any ``access_control`` entries. If this
    250+
    ``access_control`` is matched and the request is using the ``http`` channel,
    251+
    the user will be redirected to ``https``:
    252+
    253+
    .. configuration-block::
    254+
    255+
    .. code-block:: yaml
    256+
    257+
    # app/config/security.yml
    258+
    security:
    259+
    # ...
    260+
    access_control:
    261+
    - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }
    262+
    263+
    .. code-block:: xml
    264+
    265+
    <!-- app/config/security.xml -->
    266+
    <?xml version="1.0" encoding="UTF-8"?>
    267+
    <srv:container xmlns="http://symfony.com/schema/dic/security"
    268+
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    269+
    xmlns:srv="http://symfony.com/schema/dic/services"
    270+
    xsi:schemaLocation="http://symfony.com/schema/dic/services
    271+
    http://symfony.com/schema/dic/services/services-1.0.xsd">
    272+
    273+
    <access-control>
    274+
    <rule path="^/cart/checkout"
    275+
    role="IS_AUTHENTICATED_ANONYMOUSLY"
    276+
    requires-channel="https" />
    277+
    </access-control>
    278+
    </srv:container>
    279+
    280+
    .. code-block:: php
    281+
    282+
    // app/config/security.php
    283+
    $container->loadFromExtension('security', array(
    284+
    'access_control' => array(
    285+
    array(
    286+
    'path' => '^/cart/checkout',
    287+
    'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
    288+
    'requires_channel' => 'https',
    289+
    ),
    290+
    ),
    291+
    ));

    cookbook/security/form_login.rst

    Lines changed: 3 additions & 3 deletions
    Original file line numberDiff line numberDiff line change
    @@ -4,9 +4,9 @@
    44
    How to Customize your Form Login
    55
    ================================
    66

    7-
    Using a :ref:`form login <book-security-form-login>` for authentication is
    8-
    a common, and flexible, method for handling authentication in Symfony. Pretty
    9-
    much every aspect of the form login can be customized. The full, default
    7+
    Using a :doc:`form login </cookbook/security/form_login_setup>` for authentication
    8+
    is a common, and flexible, method for handling authentication in Symfony.
    9+
    Pretty much every aspect of the form login can be customized. The full, default
    1010
    configuration is shown in the next section.
    1111

    1212
    Form Login Configuration Reference

    0 commit comments

    Comments
     (0)
    0