Как работает безопасность access_control?
Дата обновления перевода 2025-09-09
Как работает безопасность access_control?
Для каждого входящего запроса, Symfony роверяет каждую запись access_control, чтобы
найти одну, соответствующую текущему запросу. Как только она находит совпадающую запись
access_control, она останавливается - только первая совпадающая access_control
используется для предоставления доступа.
Каждый access_control имеет несколько опций, которые конфигурируют две разных вещи:
- должен ли входящий запрос совпадать с этой записью управления доступа
- при совпадении, должно ли применяться какое-либо ограничение доступа :
1. Опции сопоставления
Symfony создаёт экземпляр класса RequestMatcher
для каждой записи access_control, который определяет должно ли быть использовано данное
управление доступом в этом запросе. Слелдующие опции access_control используются для
сопоставления:
path: регулярное выражение (без разграничителей)ipилиips: маски сети также поддерживаются (может быть строкой, разделенной запятой)port: целое числоhost: регулярное выражениеmethods: один или несколько методовrequest_matcher: сервис, реализующийRequestMatcherInterfaceattributes: массив, который может быть использован, чтобы указать один или более атрибутов запроса , которые должны совпадать точноroute: имя маршрута
Возьмите следующие записи access_control в качестве примера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# config/packages/security.yaml
parameters:
env(TRUSTED_IPS): '10.0.0.1, 10.0.0.2'
security:
# ...
access_control:
- { path: '^/admin', roles: ROLE_USER_PORT, ip: 127.0.0.1, port: 8080 }
- { path: '^/admin', roles: ROLE_USER_IP, ip: 127.0.0.1 }
- { path: '^/admin', roles: ROLE_USER_HOST, host: symfony\.com$ }
- { path: '^/admin', roles: ROLE_USER_METHOD, methods: [POST, PUT] }
# ip могут быть разделены запятой, что особенно полезно при использовании переменных окружения
- { path: '^/admin', roles: ROLE_USER_IP, ips: '%env(TRUSTED_IPS)%' }
- { path: '^/admin', roles: ROLE_USER_IP, ips: [127.0.0.1, ::1, '%env(TRUSTED_IPS)%'] }
# для пользовательских потребностей сопоставления, используйте сервис сопоставителя запросов
- { roles: ROLE_USER, request_matcher: App\Security\RequestMatcher\MyRequestMatcher }
# требовать ROLE_ADMIN для маршрута 'admin'. Вы можете использовать сокращение "route: "xxx", вместо "attributes": ["_route": "xxx"]
- { attributes: {'_route': 'admin'}, roles: ROLE_ADMIN }
- { route: 'admin', roles: ROLE_ADMIN }
Для каждого входящего запроса, Symfony будет решать, какой access_control
использовать, основываясь на URI, IP-адресе клиента, имени входящего хоста и
методе запроса. Помните, используется первое совпадающее правило, и если для
записи не указаны ip, port, host или method, то этот
access_control совпадёт с любым ip, port, host или method:
Рассмотрите примеры ниже:
- Пример #1:
-
- URI
/admin/user - IP:
127.0.0.1, Порт:80, Хост:example.com, Метод:GET - Правило, которое применяется: правило #2 (
ROLE_USER_IP) - Почему? URI совпадает с
path, а IP - сip.
- URI
- Пример #2:
-
- URI
/admin/user - IP:
127.0.0.1, Порт:80, Хост:symfony.com, Метод:GET - Правило, которое применяется: правило #2 (
ROLE_USER_IP) - Почему?
pathиipвсе еще совпадают. Это также будет совпадать с записьюROLE_USER_HOST, но используется только первое совпадениеaccess_control.
- URI
- Пример #3:
-
- URI
/admin/user - IP:
127.0.0.1, Порт:8080, Хост:symfony.com, Метод:GET - Правило, которое применяется*: правило #1 (
ROLE_USER_PORT) - Почему?
path,ipиportсовпадают.
- URI
- Пример #4:
-
- URI
/admin/user - IP:
168.0.0.1, Порт:80, Хост:symfony.com, Метод:GET - Правило, которое применяется: правило #3 (
ROLE_USER_HOST) - Почему?
ipне совпадает ни с первым, ни со вторым правилом. - Поэтому используется третье правило (которое совпадает).
- URI
- Пример #5:
-
- URI
/admin/user - IP:
168.0.0.1, Порт:80, Хост:symfony.com, Метод:POST - Правило, которое применяется: правило #3 (
ROLE_USER_HOST) - Почему? Третье правило все еще совпадает. Это также будет совпадать с четвертым правилом
- (
ROLE_USER_METHOD), но используется только первое совпадениеaccess_control.
- URI
- Пример #6:
-
- URI
/admin/user - IP:
168.0.0.1, Порт:80, Хост:example.com, Метод:POST - Правило, которое применяется: правило #4 (
ROLE_USER_METHOD) - Почему?
ipиhostне совпадают с первыми тремя записями, но - четвертая -
ROLE_USER_METHOD- совпадает и используется.
- URI
- Пример #7:
-
- URI
/foo - IP:
127.0.0.1, Порт:80, Хост:symfony.com, Метод:POST - Правило, которое применяется: не совпадает ни с одной записью
- Почему? Не совпадает ни с одним правилом
access_control, так как его URI - не совпадает ни с одним из значений
path.
- URI
Warning
Сопоставление URI происходит без параметров $_GET.
Откажите в доступе в PHP-кода , если вы
хотите запретить доступ, основываясь на значениях параметра $_GET.
2. Форсирование доступа
После того, как Symfony решила, какая запись access_control совпадает (если таковая есть),
она форсирует ограничения доступа, основанные на опциях roles, allow_if и requires_channel:
rolesЕсли пользователь не имеет заданной роли(, то в доступе будет отказано (внутренне, вызывается AccessDeniedException); Если это значение является массивом множества ролей, пользователь должен иметь хотя бы одну из них.allow_ifЕсли выражение возвращает "false", то в доступе будет отказано;requires_channelЕсли канал входящего запроса (например,http) не совпадает с этим значением (например,https), пользователь будет перенаправлен (например, перенаправлен сhttpнаhttps, или наоборот).
Tip
За кулисами, значение массива передается в качестве аргумента
$attributes каждому избирателю в приложении с
Request как $subject. Вы
можете узнать, как использовать ваши пользовательские атрибуты, прочитав
.
Warning
Если вы определите и roles, и allow_if, и вы используете Стратегию разрешения
доступа по умолчанию (affirmative), то пользователю будет предоставлен доступ, если
как минимум одно условие валидно. Если это поведение не подходит под ваши потребности,
измениеть Стратегию разрешения доступа .
Tip
Если в доступе отказано, система попробует аутентифицировать пользователя, если это ещё не было сделано (например, перенаправить ползователя на страницу входа). Если пользователь уже выполил вход, будет показана страница ошибки 403 "доступ запрещён". Смотрите Как настроить страницы ошибок, чтобы узнать больше.
Сопоставление access_control по IP
Некоторые ситуации могут возникнуть, когда вам нужна запись access_control,
которая совпадает только с запросами, исходящими от какого-то IP-адреса или
их спектра. Например, это может быть использовано, для отказа в доступе к
URL-шаблону для всех запросов, кроме тех, что исходят от внутреннего доверенного
сервера.
Warning
Как вы прочтёте в объяснении под примером, опция ips не ограничивается
конкретным IP-адресом. Вместо этого, использование ключа ips означает,
что запись access_control будет совпадать только с этим IP-адресом, а
пользователи, получающие доступ к ней с других IP-адресов, будут идти дальше
по списку access_control.
Вот пример того, как вы можете сконфигурировать некоторый шаблон URL /internal* так,
чтобы он был доступен только по запросам с локального сервера:
1 2 3 4 5 6 7 8
# config/packages/security.yaml
security:
# ...
access_control:
#
# опция 'ips' поддерживает IP-адреса и маски подсетей
- { path: '^/internal', roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1, 192.168.0.1/24] }
- { path: '^/internal', roles: ROLE_NO_ACCESS }
Вот, как это работает, когда путь - /internal/something исходит от внешнего
IP-адреса 10.0.0.1:
- Первое правило контроля доступа игнорируется, так как
pathсовпадает, но IP-адреса не совпадают ни с одним из перечисленных IP; - Включается второе правило контроля доступа (единственное ограничение -
path) и оно совпадает. Если вы убедитесь в том, что ни один пользователь не имеетROLE_NO_ACCESS, то в доступе будет отказано (ROLE_NO_ACCESSможет быть чем угодно, что не совпадает с существующей ролью, оно просто служит способом всегда отказывать в доступе).
Но если тот же запрос поступит от 127.0.0.1 или ::1 (адрес обратной
связи IPv6):
- Теперь, первое правило контроля доступа включается, так как совпадает и
pathиip: доступ разрешён, так как пользователь всегда имеет рольIS_AUTHENTICATED_ANONYMOUSLY. - Второе правило контроля доступа не рассматривается, так как совпало первое.
Безопасность по выражению
Когда запись access_control совпадает, вы можете отказать в доступе через ключ
roles или использовать более сложную логику с выражением в ключе allow_if:
1 2 3 4 5 6 7 8 9 10
# config/packages/security.yaml
security:
# ...
access_control:
-
path: ^/_internal/secure
# опции 'roles' и 'allow_if' работают как выражение ОС, поэтому доступ
# предоставляется, если выражение - TRUE, или если пользователь имеет ROLE_ADMIN
roles: 'ROLE_ADMIN'
allow_if: "'127.0.0.1' == request.getClientIp() or request.headers.has('X-Secure-Access')"
В этом случае, когда пользователь пытается получить доступ к любому URL,
начинающемуся с /_internal/secure, он его получит только, если IP-адрес
- 127.0.0.1, или если он имеет роль ROLE_ADMIN.
Note
Внутренне, allow_if запускает встроенный
ExpressionVoter,
как будто бы он является частью атрибутов, определенных в опции roles.
Внутри выражения, у вас есть доступ к нескольким разным переменным и функциям,
включая request, которая является объектом Symfony
Request (смотрите
).
Чтобы увидеть список других функций и переменных, смотрите functions and variables.
Tip
Выражения allow_if могут также содержать пользовательский функции, зарегистрированные
с помощью поставщиков выражений.
Ограничение по порту
Добавьте опцию port к любой записи access_control, чтобы потребовать от
пользователей получение доступа к этим URL через конкретный порт. Это может быть
ползено, к примеру, для localhost:8080.
1 2 3 4 5
# config/packages/security.yaml
security:
# ...
access_control:
- { path: ^/cart/checkout, roles: PUBLIC_ACCESS, port: 8080 }
Форсирование канала (http, https)
Вы также можете обязать пользователя получать доступ к URL через SSL; просто
используйте аргумент requires_channel в любых записях access_control.
Если access_control совпадёт, и запрос использует канал http, то пользователь
будет перенаправлен на https:
1 2 3 4 5
# config/packages/security.yaml
security:
# ...
access_control:
- { path: ^/cart/checkout, roles: PUBLIC_ACCESS, requires_channel: https }