跳到主要內容

安全性 access_control 如何運作?

編輯此頁面

對於每個傳入的請求,Symfony 會檢查每個 access_control 條目,以找到一個與當前請求匹配的條目。一旦找到匹配的 access_control 條目,它就會停止 - 僅使用第一個匹配的 access_control 來強制執行存取。

每個 access_control 都有多個選項,用於配置兩件不同的事情

  1. 傳入的請求是否應該與此存取控制條目匹配
  2. 一旦匹配,是否應該強制執行某種存取限制:

1. 比對選項

Symfony 對於每個 access_control 條目使用 ChainRequestMatcher,它決定了應該在此請求上使用哪個 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
  • IP127.0.0.1連接埠80主機example.com方法GET
  • 套用規則:規則 #2 (ROLE_USER_IP)
  • 為什麼? URI 匹配 path,IP 匹配 ip
範例 #2
  • URI /admin/user
  • IP127.0.0.1連接埠80主機symfony.com方法GET
  • 套用規則:規則 #2 (ROLE_USER_IP)
  • 為什麼? pathip 仍然匹配。這也將匹配 ROLE_USER_HOST 條目,但使用第一個 access_control 匹配。
範例 #3
  • URI /admin/user
  • IP127.0.0.1連接埠8080主機symfony.com方法GET
  • 套用規則:規則 #1 (ROLE_USER_PORT)
  • 為什麼? pathipport 匹配。
範例 #4
  • URI /admin/user
  • IP168.0.0.1連接埠80主機symfony.com方法GET
  • 套用規則:規則 #3 (ROLE_USER_HOST)
  • 為什麼? ip 與第一個規則和第二個規則都不匹配。
  • 因此使用第三個規則(匹配)。
範例 #5
  • URI /admin/user
  • IP168.0.0.1連接埠80主機symfony.com方法POST
  • 套用規則:規則 #3 (ROLE_USER_HOST)
  • 為什麼? 第三個規則仍然匹配。這也將匹配第四個規則
  • (ROLE_USER_METHOD),但僅使用第一個匹配的 access_control
範例 #6
  • URI /admin/user
  • IP168.0.0.1連接埠80主機example.com方法POST
  • 套用規則:規則 #4 (ROLE_USER_METHOD)
  • 為什麼? iphost 與前三個條目不匹配,但是
  • 第四個 - ROLE_USER_METHOD - 匹配並被使用。
範例 #7
  • URI /foo
  • IP127.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 }

以下是當路徑為 /internal/something,且來自外部 IP 位址 10.0.0.1 時的工作方式

  • 第一個存取控制規則被忽略,因為 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 授權條款授權。
目錄
    版本