安全的access_control是如何工作的?
对于每个到来的请求,Symfony都会逐个检查 Access_control
条目(entry),以找到 一个 匹配当前请求的。在找到匹配的access_control
条目后,它马上就停止了 - 只有 第一个匹配的 access_control
用于执行访问。
每个 access_control
都有若干选项,用于配置两个不同的部分:
到来的请求应当匹配此次的访问控制之条目
一旦匹配,某种形式的访问限制应当被执行
1. 匹配规则的选项 ¶
Symfony为每个 access_control
条目创建了 RequestMatcher 实例,以决定是否将给定的访问控制(access control)用于本次请求。以下是用于匹配的 access_control
选项:
path
-
ip
或ips
host
methods
将下面的 access_control
条目,作为一个示例:
1 2 3 4 5 6 7 8 |
# app/config/security.yml
security:
# ...
access_control:
- { 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] }
- { path: ^/admin, roles: ROLE_USER } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="Http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<config>
<!-- ... -->
<rule path="^/admin" role="ROLE_USER_IP" ip="127.0.0.1" />
<rule path="^/admin" role="ROLE_USER_HOST" host="symfony\.com$" />
<rule path="^/admin" role="ROLE_USER_METHOD" methods="POST, PUT" />
<rule path="^/admin" role="ROLE_USER" />
</config>
</srv:container> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// app/config/security.php
$container->loadFromExtension('security', array(
// ...
'access_control' => array(
array(
'path' => '^/admin',
'role' => 'ROLE_USER_IP',
'ip' => '127.0.0.1',
),
array(
'path' => '^/admin',
'role' => 'ROLE_USER_HOST',
'host' => 'symfony\.com$',
),
array(
'path' => '^/admin',
'role' => 'ROLE_USER_METHOD',
'methods' => 'POST, PUT',
),
array(
'path' => '^/admin',
'role' => 'ROLE_USER',
),
),
)); |
对于每个传入的请求,Symfony将基于URI、客户端IP地址、传入的主机名或请求方法(request method),来决定使用哪个 access_control
。记得,第一个被匹配到的规则(rule)将被使用,并且,如果 ip
, host
或 method
没在条目中没有被指定的话,access_control
将匹配任意的ip
, host
或 method
:
URI | IP | HOST | METHOD | access_control | Why? / 说明 |
---|---|---|---|---|---|
/admin/user |
127.0.0.1 | example.com | GET | rule #1 (ROLE_USER_IP ) |
URI 匹配 path 并且 IP 匹配 ip 。 |
/admin/user |
127.0.0.1 | symfony.com | GET | rule #1 (ROLE_USER_IP ) |
path 和 ip 仍然匹配。同时还匹配 ROLE_USER_HOST 条目,但 只有 第一个 跟
access_control 匹配的才会被使用。 |
/admin/user |
168.0.0.1 | symfony.com | GET | rule #2 (ROLE_USER_HOST ) |
ip 不匹配第一个规则 , 因此第二个规则(如果匹配的话)会被使用。 |
/admin/user |
168.0.0.1 | symfony.com | POST | rule #2 (ROLE_USER_HOST ) |
第二个规则仍然匹配。 第三个规则也能匹配 (ROLE_USER_METHOD ), 但只有 第一个 匹配的 access_control 才会被使用。 |
/admin/user |
168.0.0.1 | example.com | POST | rule #3 (ROLE_USER_METHOD ) |
ip 和 host 不能匹配到前两项, 但第三项 - ROLE_USER_METHOD - 能够匹配,并被使用。 |
/admin/user |
168.0.0.1 | example.com | GET | rule #4 (ROLE_USER ) |
The ip , host 和 method 阻止了前三个规则被匹配。但由于 path 条件能够匹配ROLE_USER条目中的URI,它会被使用。 |
/foo |
127.0.0.1 | symfony.com | POST | matches no entries | 它不能匹配任何 access_control 规则, 因为它的URL不能匹配任何一个 path 的值。 |
2. 访问控制的实施 ¶
一旦 Symfony 决定了哪些 access_control
条目能够匹配 (如果有的话),那么它将 执行 基于 roles
, allow_if
和 requires_channel
选项的访问限制:
role
如果用户不持有给定的role(s),则访问被拒绝(在内部,会抛出一个AccessDeniedException
异常);allow_if
如果表达式返回 false ,则访问被拒绝;requires_channel
如果到来的请求通道 (如http
) 无法匹配这个值 (如https
),用户会被重定向(如,从http
重定向到https
,反之亦然)。
如果访问被拒绝,若是未经认证的用户,系统将尝试去认证他(如,将他重定向到登录页面)。如果用户已经登录,将会显示403 “access denied” 的错误页面。参考 如何自定义错误页
通过IP来匹配access_control ¶
当你需要一个 access_control
条目,而它 只能 去匹配某些IP地址和IP范围的请求时,则可能面临特定情形。例如,除了 受到信任的内部服务器,其他所有的URL条件(pattern)的请求,都被拒绝访问。
正如你在下例中看到的解释,ips
选项不能限制一个特定的IP地址。相反,使用 ips
键意味着 access_control
条目将仅匹配IP地址,而用户从不同ip地址访问它时,将被 access_control
list 阻拦。
这里是关于你如何配置一些 /internal*
URL 模式例子的示例,所以它只能从本地服务器的请求来访问:
1 2 3 4 5 6 7 |
# app/config/security.yml
security:
# ...
access_control:
#
- { path: ^/internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ips: [127.0.0.1, ::1] }
- { path: ^/internal, roles: ROLE_NO_ACCESS } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<config>
<!-- ... -->
<rule path="^/internal"
role="IS_AUTHENTICATED_ANONYMOUSLY"
ips="127.0.0.1, ::1"
/>
<rule path="^/internal" role="ROLE_NO_ACCESS" />
</config>
</srv:container> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// app/config/security.php
$container->loadFromExtension('security', array(
// ...
'access_control' => array(
array(
'path' => '^/internal',
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
'ips' => '127.0.0.1, ::1'
),
array(
'path' => '^/internal',
'role' => 'ROLE_NO_ACCESS'
),
),
)); |
当外部IP地址 10.0.0.1
进入 /internal/something
路径(path)时,下面是其工作过程:
第一个访问控制规则(access control rule)将被忽略,因为
path
能匹配,但IP地址没有匹配到IP列表;第二个访问控制规则将被启用(这里只限制了
path
),所以它匹配了。如果你确保没有用户持有ROLE_NO_ACCESS
,则访问会被拒绝(ROLE_NO_ACCESS
可以是“不匹配现有role”的任何内容,它作为一种技巧而存在,用于拒绝所有访问)。
但如果相同的请求来自 127.0.0.1
或者 ::1
(IPV6 loopback address):
现在,第一个访问控制规则被启用,因为
path
和ip
都能够匹配:因为用户总是持有IS_AUTHENTICATED_ANONYMOUSLY
这个role,所以可以访问。第二个访问规则就无法通过了,因为第一个规则已经匹配。
通过表达式来保护 ¶
一旦有一个 access_control
条目被匹配, 你可以通过 roles
键,或者使用 allow_if
键下面的表达式,以形成更复杂逻辑,来拒绝访问:
1 2 3 4 5 6 7 |
# app/config/security.yml
security:
# ...
access_control:
-
path: ^/_internal/secure
allow_if: "'127.0.0.1' == request.getClientIp() or has_role('ROLE_ADMIN')" |
1 2 3 4 |
<access-control>
<rule path="^/_internal/secure"
allow-if="'127.0.0.1' == request.getClientIp() or has_role('ROLE_ADMIN')" />
</access-control> |
1 2 3 4 5 6 |
'access_control' => array(
array(
'path' => '^/_internal/secure',
'allow_if' => '"127.0.0.1" == request.getClientIp() or has_role("ROLE_ADMIN")',
),
), |
这种情况下,当用户试图访问任何以 /_internal/secure
开头的网址时,如果 IP 地址是 127.0.0.1
,或者用户拥有 ROLE_ADMIN
这个role,他们将被授权访问。
在表达式内部,你可以访问许多变量和函数,包括 request
, 它正是Symfony的 Request
对象 (见
Request)。
关于更多函数和变量,参考 函数和变量。
强制使用一种通道(http,https) ¶
你也可以要求用户通过SSL访问URL;只需在任意 access_control
条目中使用 requires_channel
参数即可。如果 access_control
被匹配,并且请求(request)使用的是 http
通道,那么用户将被重定向到 https
:
1 2 3 4 5 |
# app/config/security.yml
security:
# ...
access_control:
- { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<rule path="^/cart/checkout"
role="IS_AUTHENTICATED_ANONYMOUSLY"
requires-channel="https"
/>
</srv:container> |
1 2 3 4 5 6 7 8 9 10 |
// app/config/security.php
$container->loadFromExtension('security', array(
'access_control' => array(
array(
'path' => '^/cart/checkout',
'role' => 'IS_AUTHENTICATED_ANONYMOUSLY',
'requires_channel' => 'https',
),
),
)); |