Skip to content

Commit ce94de9

Browse files
committed
[best practice] security.rst の原文を追加
1 parent 4282486 commit ce94de9

File tree

1 file changed

+363
-0
lines changed

1 file changed

+363
-0
lines changed

best_practices/security.rst

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
Security
2+
========
3+
4+
Authentication and Firewalls (i.e. Getting the User's Credentials)
5+
------------------------------------------------------------------
6+
7+
You can configure Symfony to authenticate your users using any method you
8+
want and to load user information from any source. This is a complex topic,
9+
but the `Security Cookbook Section`_ has a lot of information about this.
10+
11+
Regardless of your needs, authentication is configured in ``security.yml``,
12+
primarily under the ``firewalls`` key.
13+
14+
.. best-practice::
15+
16+
Unless you have two legitimately different authentication systems and
17+
users (e.g. form login for the main site and a token system for your
18+
API only), we recommend having only *one* firewall entry with the ``anonymous``
19+
key enabled.
20+
21+
Most applications only have one authentication system and one set of users.
22+
For this reason, you only need *one* firewall entry. There are exceptions
23+
of course, especially if you have separated web and API sections on your
24+
site. But the point is to keep things simple.
25+
26+
Additionally, you should use the ``anonymous`` key under your firewall. If
27+
you need to require users to be logged in for different sections of your
28+
site (or maybe nearly *all* sections), use the ``access_control`` area.
29+
30+
.. best-practice::
31+
32+
Use the ``bcrypt`` encoder for encoding your users' passwords.
33+
34+
If your users have a password, then we recommend encoding it using the ``bcrypt``
35+
encoder, instead of the traditional SHA-512 hashing encoder. The main advantages
36+
of ``bcrypt`` are the inclusion of a *salt* value to protect against rainbow
37+
table attacks, and its adaptive nature, which allows to make it slower to
38+
remain resistant to brute-force search attacks.
39+
40+
With this in mind, here is the authentication setup from our application,
41+
which uses a login form to load users from the database:
42+
43+
.. code-block:: yaml
44+
45+
security:
46+
encoders:
47+
AppBundle\Entity\User: bcrypt
48+
49+
providers:
50+
database_users:
51+
entity: { class: AppBundle:User, property: username }
52+
53+
firewalls:
54+
secured_area:
55+
pattern: ^/
56+
anonymous: true
57+
form_login:
58+
check_path: security_login_check
59+
login_path: security_login_form
60+
61+
logout:
62+
path: security_logout
63+
target: homepage
64+
65+
# ... access_control exists, but is not shown here
66+
67+
.. tip::
68+
69+
The source code for our project contains comments that explain each part.
70+
71+
Authorization (i.e. Denying Access)
72+
-----------------------------------
73+
74+
Symfony gives you several ways to enforce authorization, including the ``access_control``
75+
configuration in `security.yml`_, the :ref:`@Security annotation <best-practices-security-annotation>`
76+
and using :ref:`isGranted <best-practices-directy-isGranted>` on the ``security.context``
77+
service directly.
78+
79+
.. best-practice::
80+
81+
* For protecting broad URL patterns, use ``access_control``;
82+
* Whenever possible, use the ``@Security`` annotation;
83+
* Check security directly on the ``security.context`` service whenever
84+
you have a more complex situation.
85+
86+
There are also different ways to centralize your authorization logic, like
87+
with a custom security voter or with ACL.
88+
89+
.. best-practice::
90+
91+
* For fine-grained restrictions, define a custom security voter;
92+
* For restricting access to *any* object by *any* user via an admin
93+
interface, use the Symfony ACL.
94+
95+
.. _best-practices-security-annotation:
96+
97+
The @Security Annotation
98+
------------------------
99+
100+
For controlling access on a controller-by-controller basis, use the ``@Security``
101+
annotation whenever possible. It's easy to read and is placed consistently
102+
above each action.
103+
104+
In our application, you need the ``ROLE_ADMIN`` in order to create a new post.
105+
Using ``@Security``, this looks like:
106+
107+
.. code-block:: php
108+
109+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
110+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
111+
// ...
112+
113+
/**
114+
* Displays a form to create a new Post entity.
115+
*
116+
* @Route("/new", name="admin_post_new")
117+
* @Security("has_role('ROLE_ADMIN')")
118+
*/
119+
public function newAction()
120+
{
121+
// ...
122+
}
123+
124+
Using Expressions for Complex Security Restrictions
125+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
126+
127+
If your security logic is a little bit more complex, you can use an `expression`_
128+
inside ``@Security``. In the following example, a user can only access the
129+
controller if their email matches the value returned by the ``getAuthorEmail``
130+
method on the ``Post`` object:
131+
132+
.. code-block:: php
133+
134+
use AppBundle\Entity\Post;
135+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
136+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
137+
138+
/**
139+
* @Route("/{id}/edit", name="admin_post_edit")
140+
* @Security("user.getEmail() == post.getAuthorEmail()")
141+
*/
142+
public function editAction(Post $post)
143+
{
144+
// ...
145+
}
146+
147+
Notice that this requires the use of the `ParamConverter`_, which automatically
148+
queries for the ``Post`` object and puts it on the ``$post`` argument. This
149+
is what makes it possible to use the ``post`` variable in the expression.
150+
151+
This has one major drawback: an expression in an annotation cannot easily
152+
be reused in other parts of the application. Imagine that you want to add
153+
a link in a template that will only be seen by authors. Right now you'll
154+
need to repeat the expression code using Twig syntax:
155+
156+
.. code-block:: html+jinja
157+
158+
{% if app.user and app.user.email == post.authorEmail %}
159+
<a href=""> ... </a>
160+
{% endif %}
161+
162+
The easiest solution - if your logic is simple enough - is to add a new method
163+
to the ``Post`` entity that checks if a given user is its author:
164+
165+
.. code-block:: php
166+
167+
// src/AppBundle/Entity/Post.php
168+
// ...
169+
170+
class Post
171+
{
172+
// ...
173+
174+
/**
175+
* Is the given User the author of this Post?
176+
*
177+
* @return bool
178+
*/
179+
public function isAuthor(User $user = null)
180+
{
181+
return $user && $user->getEmail() == $this->getAuthorEmail();
182+
}
183+
}
184+
185+
Now you can reuse this method both in the template and in the security expression:
186+
187+
.. code-block:: php
188+
189+
use AppBundle\Entity\Post;
190+
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
191+
192+
/**
193+
* @Route("/{id}/edit", name="admin_post_edit")
194+
* @Security("post.isAuthor(user)")
195+
*/
196+
public function editAction(Post $post)
197+
{
198+
// ...
199+
}
200+
201+
.. code-block:: html+jinja
202+
203+
{% if post.isAuthor(app.user) %}
204+
<a href=""> ... </a>
205+
{% endif %}
206+
207+
.. _best-practices-directy-isGranted:
208+
209+
Checking Permissions without @Security
210+
--------------------------------------
211+
212+
The above example with ``@Security`` only works because we're using the
213+
:ref:`ParamConverter <best-practices-paramconverter>`, which gives the expression
214+
access to the a ``post`` variable. If you don't use this, or have some other
215+
more advanced use-case, you can always do the same security check in PHP:
216+
217+
.. code-block:: php
218+
219+
/**
220+
* @Route("/{id}/edit", name="admin_post_edit")
221+
*/
222+
public function editAction($id)
223+
{
224+
$post = $this->getDoctrine()->getRepository('AppBundle:Post')
225+
->find($id);
226+
227+
if (!$post) {
228+
throw $this->createNotFoundException();
229+
}
230+
231+
if (!$post->isAuthor($this->getUser())) {
232+
throw $this->createAccessDeniedException();
233+
}
234+
235+
// ...
236+
}
237+
238+
Security Voters
239+
---------------
240+
241+
If your security logic is complex and can't be centralized into a method
242+
like ``isAuthor()``, you should leverage custom voters. These are an order
243+
of magnitude easier than `ACL's`_ and will give you the flexibility you need
244+
in almost all cases.
245+
246+
First, create a voter class. The following example shows a voter that implements
247+
the same ``getAuthorEmail`` logic you used above:
248+
249+
.. code-block:: php
250+
251+
namespace AppBundle\Security;
252+
253+
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
254+
use Symfony\Component\Security\Core\User\UserInterface;
255+
256+
// AbstractVoter class requires Symfony 2.6 or higher version
257+
class PostVoter extends AbstractVoter
258+
{
259+
const CREATE = 'create';
260+
const EDIT = 'edit';
261+
262+
protected function getSupportedAttributes()
263+
{
264+
return array(self::CREATE, self::EDIT);
265+
}
266+
267+
protected function getSupportedClasses()
268+
{
269+
return array('AppBundle\Entity\Post');
270+
}
271+
272+
protected function isGranted($attribute, $post, $user = null)
273+
{
274+
if (!$user instanceof UserInterface) {
275+
return false;
276+
}
277+
278+
if ($attribute === self::CREATE && in_array('ROLE_ADMIN', $user->getRoles(), true)) {
279+
return true;
280+
}
281+
282+
if ($attribute === self::EDIT && $user->getEmail() === $post->getAuthorEmail()) {
283+
return true;
284+
}
285+
286+
return false;
287+
}
288+
}
289+
290+
To enable the security voter in the application, define a new service:
291+
292+
.. code-block:: yaml
293+
294+
# app/config/services.yml
295+
services:
296+
# ...
297+
post_voter:
298+
class: AppBundle\Security\PostVoter
299+
public: false
300+
tags:
301+
- { name: security.voter }
302+
303+
Now, you can use the voter with the ``@Security`` annotation:
304+
305+
.. code-block:: php
306+
307+
/**
308+
* @Route("/{id}/edit", name="admin_post_edit")
309+
* @Security("is_granted('edit', post)")
310+
*/
311+
public function editAction(Post $post)
312+
{
313+
// ...
314+
}
315+
316+
You can also use this directly with the ``security.context`` service, or
317+
via the even easier shortcut in a controller:
318+
319+
.. code-block:: php
320+
321+
/**
322+
* @Route("/{id}/edit", name="admin_post_edit")
323+
*/
324+
public function editAction($id)
325+
{
326+
$post = // query for the post ...
327+
328+
if (!$this->get('security.context')->isGranted('edit', $post)) {
329+
throw $this->createAccessDeniedException();
330+
}
331+
}
332+
333+
Learn More
334+
----------
335+
336+
The `FOSUserBundle`_, developed by the Symfony community, adds support for a
337+
database-backed user system in Symfony2. It also handles common tasks like
338+
user registration and forgotten password functionality.
339+
340+
Enable the `Remember Me feature`_ to allow your users to stay logged in for
341+
a long period of time.
342+
343+
When providing customer support, sometimes it's necessary to access the application
344+
as some *other* user so that you can reproduce the problem. Symfony provides
345+
the ability to `impersonate users`_.
346+
347+
If your company uses a user login method not supported by Symfony, you can
348+
develop `your own user provider`_ and `your own authentication provider`_.
349+
350+
.. _`Security Cookbook Section`: http://symfony.com/doc/current/cookbook/security/index.html
351+
.. _`security.yml`: http://symfony.com/doc/current/reference/configuration/security.html
352+
.. _`ParamConverter`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
353+
.. _`@Security annotation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html
354+
.. _`security.yml`: http://symfony.com/doc/current/reference/configuration/security.html
355+
.. _`security voter`: http://symfony.com/doc/current/cookbook/security/voters_data_permission.html
356+
.. _`Acces Control List`: http://symfony.com/doc/current/cookbook/security/acl.html
357+
.. _`ACL's`: http://symfony.com/doc/current/cookbook/security/acl.html
358+
.. _`expression`: http://symfony.com/doc/current/components/expression_language/introduction.html
359+
.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle
360+
.. _`Remember Me feature`: http://symfony.com/doc/current/cookbook/security/remember_me.html
361+
.. _`impersonate users`: http://symfony.com/doc/current/cookbook/security/impersonating_user.html
362+
.. _`your own user provider`: http://symfony.com/doc/current/cookbook/security/custom_provider.html
363+
.. _`your own authentication provider`: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html

0 commit comments

Comments
 (0)