跳到内容

Security access_control 如何工作?

编辑此页

对于每个传入的请求,Symfony 都会检查每个 access_control 条目,以找到与当前请求匹配的一个。一旦找到匹配的 access_control 条目,它就会停止 - 只有第一个匹配的 access_control 被用于强制访问。

每个 access_control 都有几个选项,用于配置两件不同的事情:

  1. 传入的请求是否应该匹配此访问控制条目?
  2. 一旦匹配,是否应该强制执行某种访问限制?:

1. 匹配选项

Symfony 使用 ChainRequestMatcher 来处理每个 access_control 条目,它决定了应该在此请求上使用 RequestMatcherInterface 的哪个实现。以下 access_control 选项用于匹配:

  • path: 一个正则表达式 (没有分隔符)
  • ipips: 也支持网络掩码 (可以是逗号分隔的字符串)
  • 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。请记住,使用第一个匹配的规则,并且如果未为条目指定 ipporthostmethod,则该 access_control 将匹配任何 ipporthostmethod。请参阅以下示例:

示例 #1
  • URI /admin/user
  • IP: 127.0.0.1, 端口: 80, 主机: example.com, 方法: GET
  • 应用的规则:规则 #2 (ROLE_USER_IP)
  • 为什么? URI 匹配 path,IP 匹配 ip
示例 #2
  • URI /admin/user
  • IP: 127.0.0.1, 端口: 80, 主机: symfony.com, 方法: GET
  • 应用的规则:规则 #2 (ROLE_USER_IP)
  • 为什么? pathip 仍然匹配。这也将匹配 ROLE_USER_HOST 条目,但只有第一个 access_control 匹配会被使用。
示例 #3
  • URI /admin/user
  • IP: 127.0.0.1, 端口: 8080, 主机: symfony.com, 方法: GET
  • 应用的规则:规则 #1 (ROLE_USER_PORT)
  • 为什么? pathipport 都匹配。
示例 #4
  • URI /admin/user
  • IP: 168.0.0.1, 端口: 80, 主机: symfony.com, 方法: GET
  • 应用的规则:规则 #3 (ROLE_USER_HOST)
  • 为什么? ip 既不匹配第一个规则也不匹配第二个规则。
  • 因此,使用第三个规则(它匹配)。
示例 #5
  • URI /admin/user
  • IP: 168.0.0.1, 端口: 80, 主机: symfony.com, 方法: POST
  • 应用的规则:规则 #3 (ROLE_USER_HOST)
  • 为什么? 第三个规则仍然匹配。这也将匹配第四个规则
  • (ROLE_USER_METHOD),但仅使用第一个匹配的 access_control
示例 #6
  • URI /admin/user
  • IP: 168.0.0.1, 端口: 80, 主机: example.com, 方法: POST
  • 应用的规则:规则 #4 (ROLE_USER_METHOD)
  • 为什么? iphost 不匹配前三个条目,但是
  • 第四个 - ROLE_USER_METHOD - 匹配并被使用。
示例 #7
  • URI /foo
  • IP: 127.0.0.1, 端口: 80, 主机: symfony.com, 方法: POST
  • 应用的规则:不匹配任何条目
  • 为什么? 这不匹配任何 access_control 规则,因为它的 URI
  • 不匹配任何 path 值。

警告

URI 的匹配是在没有 $_GET 参数的情况下完成的。如果您想根据 $_GET 参数值拒绝访问,请在 PHP 代码中拒绝访问

2. 访问控制

一旦 Symfony 决定了哪个 access_control 条目匹配(如果有),它就会根据 rolesallow_ifrequires_channel 选项强制执行访问限制:

  • roles:如果用户没有给定的角色,则拒绝访问(在内部,会抛出一个 AccessDeniedException 异常)。
  • allow_if:如果表达式返回 false,则拒绝访问;
  • requires_channel:如果传入请求的通道(例如 http)与此值(例如 https)不匹配,则用户将被重定向(例如,从 http 重定向到 https,或反之亦然)。

提示

在幕后,roles 的数组值作为 $attributes 参数传递给应用程序中的每个投票器,并将 Request 作为 $subject 传递。您可以通过阅读 如何使用投票器检查用户权限来了解如何使用自定义属性。

警告

如果您同时定义了 rolesallow_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 回环地址):

  • 现在,第一个访问控制规则被启用,因为 pathip 都匹配:由于用户始终具有 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 }
这项工作,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本