Security access_control 如何工作?
对于每个传入的请求,Symfony 都会检查每个 access_control
条目,以找到与当前请求匹配的一个。一旦找到匹配的 access_control
条目,它就会停止 - 只有第一个匹配的 access_control
被用于强制访问。
每个 access_control
都有几个选项,用于配置两件不同的事情:
1. 匹配选项
Symfony 使用 ChainRequestMatcher 来处理每个 access_control
条目,它决定了应该在此请求上使用 RequestMatcherInterface 的哪个实现。以下 access_control
选项用于匹配:
path
: 一个正则表达式 (没有分隔符)ip
或ips
: 也支持网络掩码 (可以是逗号分隔的字符串)port
: 一个整数host
: 一个正则表达式methods
: 一个或多个 HTTP 方法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] }
# ips can be comma-separated, which is especially useful when using env variables
- { 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)%'] }
# for custom matching needs, use a request matcher service
- { roles: ROLE_USER, request_matcher: App\Security\RequestMatcher\MyRequestMatcher }
# require ROLE_ADMIN for 'admin' route. You can use the shortcut "route: "xxx", instead of "attributes": ["_route": "xxx"]
- { attributes: {'_route': 'admin'}, roles: ROLE_ADMIN }
- { route: 'admin', roles: ROLE_ADMIN }
对于每个传入的请求,Symfony 将根据 URI、客户端的 IP 地址、传入的主机名和请求方法来决定使用哪个 access_control
。请记住,使用第一个匹配的规则,并且如果未为条目指定 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
警告
URI 的匹配是在没有 $_GET
参数的情况下完成的。如果您想根据 $_GET
参数值拒绝访问,请在 PHP 代码中拒绝访问。
2. 访问控制
一旦 Symfony 决定了哪个 access_control
条目匹配(如果有),它就会根据 roles
、allow_if
和 requires_channel
选项强制执行访问限制:
roles
:如果用户没有给定的角色,则拒绝访问(在内部,会抛出一个 AccessDeniedException 异常)。allow_if
:如果表达式返回 false,则拒绝访问;requires_channel
:如果传入请求的通道(例如http
)与此值(例如https
)不匹配,则用户将被重定向(例如,从http
重定向到https
,或反之亦然)。
提示
在幕后,roles
的数组值作为 $attributes
参数传递给应用程序中的每个投票器,并将 Request 作为 $subject
传递。您可以通过阅读 如何使用投票器检查用户权限来了解如何使用自定义属性。
警告
如果您同时定义了 roles
和 allow_if
,并且您的访问决策策略是默认策略 (affirmative
),那么如果至少有一个有效条件,则用户将被授予访问权限。如果此行为不符合您的需求,请更改访问决策策略。
提示
如果访问被拒绝,系统将尝试对用户进行身份验证(如果尚未进行身份验证)(例如,将用户重定向到登录页面)。如果用户已登录,则会显示 403 “拒绝访问”错误页面。有关更多信息,请参阅如何自定义错误页面。
通过 IP 匹配 access_control
在某些情况下,您可能需要一个 access_control
条目,该条目仅匹配来自某些 IP 地址或范围的请求。例如,这可以用于拒绝所有请求(除了来自受信任的内部服务器的请求)访问 URL 模式。
警告
正如您将在示例下面的解释中读到的那样,ips
选项不会限制为特定的 IP 地址。相反,使用 ips
键意味着 access_control
条目将仅匹配此 IP 地址,并且从不同 IP 地址访问它的用户将继续向下遍历 access_control
列表。
以下示例说明了如何配置一些示例 /internal*
URL 模式,使其仅可由来自本地服务器本身的请求访问:
1 2 3 4 5 6 7 8
# config/packages/security.yaml
security:
# ...
access_control:
#
# the 'ips' option supports IP addresses and subnet masks
- { path: '^/internal', roles: PUBLIC_ACCESS, ips: [127.0.0.1, ::1, 192.168.0.1/24] }
- { path: '^/internal', roles: ROLE_NO_ACCESS }
以下是当路径是来自外部 IP 地址 10.0.0.1
的 /internal/something
时的工作方式:
- 第一个访问控制规则被忽略,因为
path
匹配,但 IP 地址与列出的任何 IP 都不匹配; - 第二个访问控制规则被启用(唯一的限制是
path
),因此它匹配。如果您确保没有任何用户拥有ROLE_NO_ACCESS
,则访问被拒绝(ROLE_NO_ACCESS
可以是不匹配现有角色的任何内容,它仅用作始终拒绝访问的技巧)。
但是,如果相同的请求来自 127.0.0.1
或 ::1
(IPv6 回环地址):
- 现在,第一个访问控制规则被启用,因为
path
和ip
都匹配:由于用户始终具有PUBLIC_ACCESS
角色,因此允许访问。 - 由于第一个规则已匹配,因此不会检查第二个访问规则。
通过表达式进行安全控制
一旦匹配了 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
# the 'roles' and 'allow_if' options work like an OR expression, so
# access is granted if the expression is TRUE or the user has ROLE_ADMIN
roles: 'ROLE_ADMIN'
allow_if: "'127.0.0.1' == request.getClientIp() or request.headers.has('X-Secure-Access')"
在这种情况下,当用户尝试访问任何以 /_internal/secure
开头的 URL 时,只有当 IP 地址为 127.0.0.1
或安全标头,或者用户具有 ROLE_ADMIN
角色时,他们才会被授予访问权限。
注意
在内部,allow_if
触发内置的 ExpressionVoter,就像它是 roles
选项中定义的属性的一部分一样。
在表达式内部,您可以访问许多不同的变量和函数,包括 request
,它是 Symfony Request 对象(请参阅 HttpFoundation 组件)。
有关其他函数和变量的列表,请参阅函数和变量。
提示
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)
您还可以要求用户通过 SSL 访问 URL;在任何 access_control
条目中使用 requires_channel
参数。如果匹配了此 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 }