安全性 access_control 如何運作?
對於每個傳入的請求,Symfony 會檢查每個 access_control
條目,以找到一個與當前請求匹配的條目。一旦找到匹配的 access_control
條目,它就會停止 - 僅使用第一個匹配的 access_control
來強制執行存取。
每個 access_control
都有多個選項,用於配置兩件不同的事情
1. 比對選項
Symfony 對於每個 access_control
條目使用 ChainRequestMatcher,它決定了應該在此請求上使用哪個 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 }
以下是當路徑為 /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
都匹配:由於使用者始終擁有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 }