Как работает безопасность access_control?
Дата обновления перевода 2024-07-27
Как работает безопасность 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
: сервис, реализующийRequestMatcherInterface
attributes
: массив, который может быть использован, чтобы указать один или более атрибутов запроса , которые должны совпадать точно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
Caution
Сопоставление 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
. Вы
можете узнать, как использовать ваши пользовательские атрибуты, прочитав
.
Caution
Если вы определите и roles
, и allow_if
, и вы используете Стратегию разрешения
доступа по умолчанию (affirmative
), то пользователю будет предоставлен доступ, если
как минимум одно условие валидно. Если это поведение не подходит под ваши потребности,
измениеть Стратегию разрешения доступа .
Tip
Если в доступе отказано, система попробует аутентифицировать пользователя, если это ещё не было сделано (например, перенаправить ползователя на страницу входа). Если пользователь уже выполил вход, будет показана страница ошибки 403 "доступ запрещён". Смотрите Как настроить страницы ошибок, чтобы узнать больше.
Сопоставление access_control по IP
Некоторые ситуации могут возникнуть, когда вам нужна запись access_control
,
которая совпадает только с запросами, исходящими от какого-то IP-адреса или
их спектра. Например, это может быть использовано, для отказа в доступе к
URL-шаблону для всех запросов, кроме тех, что исходят от внутреннего доверенного
сервера.
Caution
Как вы прочтёте в объяснении под примером, опция 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 }