Безопасность: Сложный контроль доступа с выражениями

Дата обновления перевода 2024-07-27

Безопасность: Сложный контроль доступа с выражениями

See also

Наилучшим решением для работы со сложными правилами авторизации является использование Системы избирателей.

В дополнение к роли вроде ROLE_ADMIN, метод isGranted() также принимает объект Expression:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Controller/MyController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsGranted;

class MyController extends AbstractController
{
    #[IsGranted(new Expression('is_granted("ROLE_ADMIN") or is_granted("ROLE_MANAGER")'))]
    public function show(): Response
    {
        // ...
    }

    #[IsGranted(new Expression(
        '"ROLE_ADMIN" in role_names or (is_authenticated() and user.isSuperAdmin())'
    ))]
    public function edit(): Response
    {
        // ...
    }
}

В этом примере, если текущий пользователь имеет ROLE_ADMIN или если метод объекта текущего пользователя isSuperAdmin() возвращает true, то доступ будет гарантирован (примечание: ваш объект Пользователь может не иметь метода isSuperAdmin(), этот метод был выдуман для данного примера).

Выражение безопасности должно использовать любой валидный синтаксис языка выражений и может использовать любую из этих переменных, созданных Symfony:

user
Экземпляр UserInterface, который представляет текущего пользователя или null, если вы не аутентифицированы.
role_names
Массив со строковым представлением ролей, которые имеет пользователь. Этот массив содержит любые роли, предоставленные косвенно через иерархию ролей , но не содержит атрибуты IS_AUTHENTICATED_* (см. функции ниже).
object
Объект (если таковой имеется), переданный в качестве второго аргумента в isGranted().
subject
Он хранит то же значение, что и object, поэтому они эквивалентны.
token
Объект токена.
trust_resolver
Объект AuthenticationTrustResolverInterface: вместо этого вы, вероятно, будете использовать функции is_*() ниже.

Кроме того, у вас есть доступ к следующим функциям внутри выражения:

is_authenticated()
Возвращает true, если пользователь аутентифицирован через "запомнить меня" или "полностью" аутентифицирован - т.е. возвращает "true", если пользователь находится в системе.
is_remember_me()
Похоже на, но не эквивалентно IS_AUTHENTICATED_REMEMBERED, см. ниже.
is_fully_authenticated()
Похоже, но не эквивалентно IS_AUTHENTICATED_FULLY, см. ниже.
is_granted()
Проверяет, есть ли у пользователя заданное разрешение. Дополнительно принимает второй аргмент с объектом, где проверяется разрешение. Эквивалентно использованию метода isGranted() из сервиса безопасности.

Функции is_remember_me() и is_authenticated_fully() похожи на использование IS_AUTHENTICATED_REMEMBERED и IS_AUTHENTICATED_FULLY с функцией isGranted(), но они не одинаковы. Следующий отрезок из контроллера демонстрирует отличия:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
// ...

public function index(AuthorizationCheckerInterface $authorizationChecker): Response
{
    $access1 = $authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED');

    $access2 = $authorizationChecker->isGranted(new Expression(
        'is_remember_me() or is_fully_authenticated()'
    ));
}

Здесь, $access1 и $access2 будут иметь одинаковое значение. В отличие от поведения IS_AUTHENTICATED_REMEMBERED и IS_AUTHENTICATED_FULLY, функция is_remember_me() возвращает "ture" только, если пользователь аутентифицирован через куки "запомнить меня" и is_fully_authenticated, возвращает "true" только, если пользователь вошел в систему в течение этой сессии (т.е. полнофункциональный).

В случае атрибута #[IsGranted()] субъектом также может быть объект Expression:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/MyController.php
namespace App\Controller;

use App\Entity\Post;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Attribute\IsGranted;

class MyController extends AbstractController
{
    #[IsGranted(
        attribute: new Expression('user === subject'),
        subject: new Expression('args["post"].getAuthor()'),
    )]
    public function index(Post $post): Response
    {
        // ...
    }
}

В этом примере мы получаем автора поста и используем его как субъект. Если субъект совпадает с текущим пользователем, то доступ будет предоставлен.

Субъект также может быть массивом, где ключ можно использовать как псевдоним для результата выражения:

1
2
3
4
5
6
7
8
9
10
11
#[IsGranted(
    attribute: new Expression('user === subject["author"] and subject["post"].isPublished()'),
    subject: [
        'author' => new Expression('args["post"].getAuthor()'),
        'post',
    ],
)]
public function index(Post $post): Response
{
    // ...
}

Здесь доступ будет предоставлен, если автор совпадает с текущим пользователем и метод поста isPublished() возвращает true.

Вы также можете использовать текущий запрос как субъект:

1
2
3
4
5
6
7
8
#[IsGranted(
    attribute: '...',
    subject: new Expression('request'),
)]
public function index(): Response
{
    // ...
}

Внутри выражения субъекта вы имеете доступ к двум переменным:

request
Объект Symfony Request , который представляет текущий запрос.
args
Массив аргументов контроллера, которые передаются контроллеру.