@@ -74,14 +74,16 @@ Authorization (i.e. Denying Access)
74
74
-----------------------------------
75
75
76
76
Symfony gives you several ways to enforce authorization, including the ``access_control ``
77
- configuration in :doc: `security.yml </reference/configuration/security >` and
78
- using :ref: `isGranted <best-practices-directly-isGranted >` on the ``security.context ``
77
+ configuration in :doc: `security.yml </reference/configuration/security >`, the
78
+ :ref: `@Security annotation <best-practices-security-annotation >` and using
79
+ :ref: `isGranted <best-practices-directly-isGranted >` on the ``security.authorization_checker ``
79
80
service directly.
80
81
81
82
.. best-practice ::
82
83
83
84
* For protecting broad URL patterns, use ``access_control ``;
84
- * Check security directly on the ``security.context `` service whenever
85
+ * Whenever possible, use the ``@Security `` annotation;
86
+ * Check security directly on the ``security.authorization_checker `` service whenever
85
87
you have a more complex situation.
86
88
87
89
There are also different ways to centralize your authorization logic, like
@@ -93,21 +95,132 @@ with a custom security voter or with ACL.
93
95
* For restricting access to *any * object by *any * user via an admin
94
96
interface, use the Symfony ACL.
95
97
96
- .. _best-practices-directly-isGranted :
97
- .. _checking-permissions-without-security :
98
+ .. _best-practices-security-annotation :
99
+
100
+ The @Security Annotation
101
+ ------------------------
98
102
99
- Manually Checking Permissions
100
- -----------------------------
103
+ For controlling access on a controller-by-controller basis, use the ``@Security ``
104
+ annotation whenever possible. It's easy to read and is placed consistently
105
+ above each action.
101
106
102
- If you cannot control the access based on URL patterns, you can always do
103
- the security checks in PHP :
107
+ In our application, you need the `` ROLE_ADMIN `` in order to create a new post.
108
+ Using `` @Security ``, this looks like :
104
109
105
110
.. code-block :: php
106
111
107
- use Symfony\Component\Security\Core\Exception\AccessDeniedException;
112
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
113
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
114
+ // ...
115
+
116
+ /**
117
+ * Displays a form to create a new Post entity.
118
+ *
119
+ * @Route("/new", name="admin_post_new")
120
+ * @Security("has_role('ROLE_ADMIN')")
121
+ */
122
+ public function newAction()
123
+ {
124
+ // ...
125
+ }
126
+
127
+ Using Expressions for Complex Security Restrictions
128
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
129
+
130
+ If your security logic is a little bit more complex, you can use an `expression `_
131
+ inside ``@Security ``. In the following example, a user can only access the
132
+ controller if their email matches the value returned by the ``getAuthorEmail ``
133
+ method on the ``Post `` object:
134
+
135
+ .. code-block :: php
136
+
137
+ use AppBundle\Entity\Post;
138
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
139
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
140
+
141
+ /**
142
+ * @Route("/{id}/edit", name="admin_post_edit")
143
+ * @Security("user.getEmail() == post.getAuthorEmail()")
144
+ */
145
+ public function editAction(Post $post)
146
+ {
147
+ // ...
148
+ }
149
+
150
+ Notice that this requires the use of the `ParamConverter `_, which automatically
151
+ queries for the ``Post `` object and puts it on the ``$post `` argument. This
152
+ is what makes it possible to use the ``post `` variable in the expression.
153
+
154
+ This has one major drawback: an expression in an annotation cannot easily
155
+ be reused in other parts of the application. Imagine that you want to add
156
+ a link in a template that will only be seen by authors. Right now you'll
157
+ need to repeat the expression code using Twig syntax:
158
+
159
+ .. code-block :: html+jinja
160
+
161
+ {% if app.user and app.user.email == post.authorEmail %}
162
+ <a href=""> ... </a>
163
+ {% endif %}
108
164
165
+ The easiest solution - if your logic is simple enough - is to add a new method
166
+ to the ``Post `` entity that checks if a given user is its author:
167
+
168
+ .. code-block :: php
169
+
170
+ // src/AppBundle/Entity/Post.php
109
171
// ...
110
172
173
+ class Post
174
+ {
175
+ // ...
176
+
177
+ /**
178
+ * Is the given User the author of this Post?
179
+ *
180
+ * @return bool
181
+ */
182
+ public function isAuthor(User $user = null)
183
+ {
184
+ return $user && $user->getEmail() == $this->getAuthorEmail();
185
+ }
186
+ }
187
+
188
+ Now you can reuse this method both in the template and in the security expression:
189
+
190
+ .. code-block :: php
191
+
192
+ use AppBundle\Entity\Post;
193
+ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
194
+
195
+ /**
196
+ * @Route("/{id}/edit", name="admin_post_edit")
197
+ * @Security("post.isAuthor(user)")
198
+ */
199
+ public function editAction(Post $post)
200
+ {
201
+ // ...
202
+ }
203
+
204
+ .. code-block :: html+jinja
205
+
206
+ {% if post.isAuthor(app.user) %}
207
+ <a href=""> ... </a>
208
+ {% endif %}
209
+
210
+ .. _best-practices-directly-isGranted :
211
+ .. _checking-permissions-without-security :
212
+ .. _manually-checking-permissions :
213
+
214
+ Checking Permissions without @Security
215
+ --------------------------------------
216
+
217
+ The above example with ``@Security `` only works because we're using the
218
+ :ref: `ParamConverter <best-practices-paramconverter >`, which gives the expression
219
+ access to the a ``post `` variable. If you don't use this, or have some other
220
+ more advanced use-case, you can always do the same security check in PHP:
221
+
222
+ .. code-block :: php
223
+
111
224
/**
112
225
* @Route("/{id}/edit", name="admin_post_edit")
113
226
*/
@@ -121,7 +234,16 @@ the security checks in PHP:
121
234
}
122
235
123
236
if (!$post->isAuthor($this->getUser())) {
124
- throw new AccessDeniedException();
237
+ $this->denyAccessUnlessGranted('edit', $post);
238
+
239
+ // or without the shortcut:
240
+ //
241
+ // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
242
+ // ...
243
+ //
244
+ // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
245
+ // throw $this->createAccessDeniedException();
246
+ // }
125
247
}
126
248
127
249
// ...
@@ -192,13 +314,23 @@ To enable the security voter in the application, define a new service:
192
314
tags :
193
315
- { name: security.voter }
194
316
195
- Now, you can use the voter with the ``security.context `` service :
317
+ Now, you can use the voter with the ``@Security `` annotation :
196
318
197
319
.. code-block :: php
198
320
199
- use Symfony\Component\Security\Core\Exception\AccessDeniedException;
321
+ /**
322
+ * @Route("/{id}/edit", name="admin_post_edit")
323
+ * @Security("is_granted('edit', post)")
324
+ */
325
+ public function editAction(Post $post)
326
+ {
327
+ // ...
328
+ }
200
329
201
- // ...
330
+ You can also use this directly with the ``security.authorization_checker `` service or
331
+ via the even easier shortcut in a controller:
332
+
333
+ .. code-block :: php
202
334
203
335
/**
204
336
* @Route("/{id}/edit", name="admin_post_edit")
@@ -207,9 +339,16 @@ Now, you can use the voter with the ``security.context`` service:
207
339
{
208
340
$post = // query for the post ...
209
341
210
- if (!$this->get('security.context')->isGranted('edit', $post)) {
211
- throw new AccessDeniedException();
212
- }
342
+ $this->denyAccessUnlessGranted('edit', $post);
343
+
344
+ // or without the shortcut:
345
+ //
346
+ // use Symfony\Component\Security\Core\Exception\AccessDeniedException;
347
+ // ...
348
+ //
349
+ // if (!$this->get('security.authorization_checker')->isGranted('edit', $post)) {
350
+ // throw $this->createAccessDeniedException();
351
+ // }
213
352
}
214
353
215
354
Learn More
@@ -230,4 +369,7 @@ If your company uses a user login method not supported by Symfony, you can
230
369
develop :doc: `your own user provider </cookbook/security/custom_provider >` and
231
370
:doc: `your own authentication provider </cookbook/security/custom_authentication_provider >`.
232
371
372
+ .. _`ParamConverter` : http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html
373
+ .. _`@Security annotation` : http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/security.html
374
+ .. _`expression` : http://symfony.com/doc/current/components/expression_language/introduction.html
233
375
.. _`FOSUserBundle` : https://github.com/FriendsOfSymfony/FOSUserBundle
0 commit comments