安全(Security)


Symfony的安全系統(security system)是非常強大的,但在設定它時也可能令人困惑。在本大章中,你將學會如何一步步地設定程式的security,從配置防火牆(firewall)以及載入用戶,到拒絕存取和取到用戶物件。根據你的需求,有時在進行初始化設定時是痛苦的。然而一旦(配置)完成,Symfony的security系統在使用時,既靈活又(希望能夠)有樂趣。

由於有很多話要說,本章按幾個大部頭來組織:

  1. #初始化security.yml 的設定(authentication/驗證 );

  2. 拒絕存取你的程式(authorization/授權 );

取得目前的User物件;

它們被細分為許多小塊內容(但仍然令人著迷),像是logging out

加密用戶密碼

1)初始化security.yml的設定(Authentication/驗證) security系統在 app/config/security.yml 中進行配置。預設的配置是這樣的:

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    'providers' => array(
        'in_memory' => array(
            'memory' => null,
        ),
    ),
    'firewalls' => array(
        'dev' => array(
            'pattern'    => '^/(_(profiler|wdt)|css|images|js)/',
            'security'   => false,
        ),
        'default' => array(
            'anonymous'  => null,
        ),
    ),));
XML:<!-- 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>
        <provider name="in_memory">
            <memory />
        </provider>         <firewall name="dev"            pattern="^/(_(profiler|wdt)|css|images|js)/"            security="false" />         <firewall name="default">
            <anonymous />
        </firewall>
    </config></srv:container>
YAML:# app/config/security.ymlsecurity:
    providers:
        in_memory:
            memory: ~
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        default:
            anonymous: ~
firewalls 鍵是security配置的 核心

dev

防火牆並不重要,它只是確保Symfony開發工具- 也就是居於11.png/_profiler

/_wdt

之下的那些URL將不會被你的security所阻止。

你也可以針對請求中的其他細節來匹配一個請求(如 host主機)。閱讀 如何將防火牆限制在特定請求 以了解更多內容和例程。

所有其他的URL將被###default### 防火牆中處理(沒有###pattern### 鍵意味著它可以符合###所有### URL)。你可以認為防火牆就是你的security系統,因此通常只有一個主力防火牆就變得有意義了。但這並###不### 意味著每一個URL都需要驗證 - ###anonymous### 鍵可以搞定這個。事實上,如果你現在去首頁,是可以訪問的,並將看到你以 ###anon.### 身份「通過了驗證」。不要被Authenticated旁邊的「Yes」愚弄,你仍然只是個匿名用戶:###############後面你將學習到如何拒絕訪問特定URL或控制器(中的action) 。 ############Security是###高### 可設定的,在 ###Security設定參考### 裡展示了整個設定選項,並且有一些附加說明。 ##########

A) 設定「如何讓使用者接受驗證」 

防火牆的主要工作是去設定如何 讓你的使用者能夠被驗證。它們是否需要使用登入表單?還是HTTP basic驗證?抑或一個API token?還是上面所有這些?

讓我們從HTTP basic驗證開始(老舊的彈出框)展開。要啟動此功能,請添加 http_basic 到firewall下面:

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...
    'firewalls' => array(
        // ...
        'default' => array(
            'anonymous'  => null,
            'http_basic' => null,
        ),
    ),));
XML:<!-- 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>
        <!-- ... -->         <firewall name="default">
            <anonymous />
            <http-basic />
        </firewall>
    </config></srv:container>
YAML:# app/config/security.ymlsecurity:    # ...
    firewalls:        # ...
        default:
            anonymous: ~
            http_basic: ~
真簡單!要測試效果,你需要讓使用者登入並看到頁面。為了讓事情變的有趣,在 /admin 下建立一個新頁面 。例如,如果你使用annotations,建立下例程式碼:
// src/AppBundle/Controller/DefaultController.php
// ... use 
Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Symfony\Component\HttpFoundation\Response; class DefaultController extends Controller{
   /**
     * @Route("/admin")
     */
    public function adminAction()
    {
        return new Response('<html><body>Admin page!</body></html>');
    }}

接下來,在security.yml 中新增一個access_control 入口,以令用戶必先登入方可存取URL:

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...
    'firewalls' => array(
        // ...
        'default' => array(
            // ...
        ),
    ),
   'access_control' => array(
       // require ROLE_ADMIN for /admin*
        array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
    ),));
XML:<!-- 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>
        <!-- ... -->         <firewall name="default">
            <!-- ... -->
        </firewall>         <!-- require ROLE_ADMIN for /admin* -->
        <rule path="^/admin" role="ROLE_ADMIN" />
    </config></srv:container>
YAML:# app/config/security.ymlsecurity:    # ...
    firewalls:        # ...
        default:            # ...
    access_control:        # require ROLE_ADMIN for /admin*
        - { path: ^/admin, roles: ROLE_ADMIN }

你將學到關於ROLE_ADMIN 以及後面的2 ) 拒絕存取, Roles和其他授權 小節中的「拒絕存取」等更多內容。

太好了,如果你去造訪/admin,會看到HTTP Basic驗證的彈出式登入:

222.png

但是,你是以何種身分登入進來?用戶(資訊)又來自哪裡呢?

想要使用一個傳統的form表單?很好!參考 如何建立一個傳統的登入表單。還支援其他什麼(驗證)方式?參考 Configuration Reference建立你自己的

如果你的程式是透過Google,Facebook或Twitter等三方服務來完成登錄,請參閱 HWIOAuthBundle 社群bundle。

#

B) 設定「如何載入使用者」 

當你在輸入使用者名稱時,Symfony就要從某個地方載入使用者的資訊。這就是所謂的“user provider”,由你來負責配置它。 Symfony有一個內建方式來 從資料庫載入使用者,但也可以 建立你自己的user provider

最簡單的方式(但有許多限制),是設定Symfony從 security.yml 檔案裡直接載入寫死在其中的使用者。這被稱為“in memory” provider,但把它觀想為“in configuration” provider更合適些

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    'providers' => array(
        'in_memory' => array(
            'memory' => array(
                'users' => array(
                    'ryan' => array(
                        'password' => 'ryanpass',
                        'roles' => 'ROLE_USER',
                    ),
                    'admin' => array(
                        'password' => 'kitten',
                        'roles' => 'ROLE_ADMIN',
                    ),
                ),
            ),
        ),
    ),
    // ...));
XML:<!-- 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>
        <provider name="in_memory">
            <memory>
                <user name="ryan" password="ryanpass" roles="ROLE_USER" />
                <user name="admin" password="kitten" roles="ROLE_ADMIN" />
            </memory>
        </provider>
        <!-- ... -->
    </config></srv:container>
YAML:# app/config/security.ymlsecurity:
    providers:
        in_memory:
            memory:
                users:
                    ryan:
                        password: ryanpass
                        roles: 'ROLE_USER'
                    admin:
                        password: kitten
                        roles: 'ROLE_ADMIN'    # ...

類似firewalls,你可以擁有多個providers ,但你幾乎只需要一個。如果你確實 擁有多個,你可以在防火牆的provider 鍵(如provider:in_memory)之下,設定「要使用哪一個一個”provider。

參考 如何使用多個User Providers 來了解multiple providers設定的全部細節。

#

試著以使用者名稱 admin 和密碼 kitten 來登入。你應該看到一個錯誤!

No encoder has been configured for account "Symfony\Component\Security\Core\User\User"

要修正此問題,新增一個encoders 健:

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...     'encoders' => array(
        'Symfony\Component\Security\Core\User\User' => 'plaintext',
    ),
    // ...));
XML:<!-- 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>
        <!-- ... -->         <encoder class="Symfony\Component\Security\Core\User\User"            algorithm="plaintext" />
        <!-- ... -->
    </config></srv:container>
YAML:app/config/security.ymlsecurity:    # ...
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext    # ...

User providers載入使用者訊息,並將其置於一個User 物件中。如果你從資料庫載入使用者從其他來源載入,你需要使用自訂的User類別。但當你使用「in memory」 provider時,它直接給了你一個 Symfony\Component\Security\Core\User\User 物件。

無論你使用什麼樣的User類,你都需要去告訴Symfony它們的密碼加密演算法是什麼。在本例中,密碼只是明文的文本,但很快,你要把它改成 bcrypt

如果你現在刷新,你就會登入進來! web調試工具列會告訴你,你是何人,你的roles(角色)是什麼:

4444t.png

#由於此URL需要的是ROLE_ADMIN# ,如果你登入的是ryan 用戶,將被拒絕存取。更多內容參考後面的[URL受到保護的條件(access_control](#catalog9)。

從資料庫載入使用者 

如果你想透過Doctrine Orm加載用戶,很容易!參考如何從資料庫(Entity Provider)載入Security系統之用戶 以了解全部細節。

C) 對用戶密碼進行加密 

#不管使用者儲存在security.yml 裡,資料庫裡還是其他地方,你都需要去加密其密碼。堪用的最好演算法是bcrypt

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...     'encoders' => array(
        'Symfony\Component\Security\Core\User\User' => array(
            'algorithm' => 'bcrypt',
            'cost' => 12,
        )
    ),
    // ...));
XML:<!-- 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>        <!-- ... -->         <encoder class="Symfony\Component\Security\Core\User\User"            algorithm="bcrypt"            cost="12" />         <!-- ... -->    </config></srv:container>


#
YAML:# app/config/security.ymlsecurity:    # ...
    encoders:
        Symfony\Component\Security\Core\User\User:
            algorithm: bcrypt
            cost: 12


#當然,使用者密碼現在需要被這個指定算法加密。對於寫死(在config.yml中)的用戶,你可以用內建指令來完成:

#

它將帶給你下面這樣(密碼被加密)的東東:

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...     'providers' => array(
        'in_memory' => array(
            'memory' => array(
                'users' => array(
                    'ryan' => array(
                        'password' => 'a$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli',
                        'roles' => 'ROLE_USER',
                    ),
                    'admin' => array(
                        'password' => 'a$cyTWeE9kpq1PjqKFiWUZFuCRPwVyAZwm4XzMZ1qPUFl7/flCM3V0G',
                        'roles' => 'ROLE_ADMIN',
                    ),
                ),
            ),
        ),
    ),
    // ...));
XML:<!-- 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>
        <!-- ... -->         <provider name="in_memory">
            <memory>
                <user name="ryan" password="a$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli" roles="ROLE_USER" />
                <user name="admin" password="a$cyTWeE9kpq1PjqKFiWUZFuCRPwVyAZwm4XzMZ1qPUFl7/flCM3V0G" roles="ROLE_ADMIN" />
            </memory>
        </provider>
    </config></srv:container>
YAML:# app/config/security.ymlsecurity:    # ...
    providers:
        in_memory:
            memory:
                users:
                    ryan:
                        password: a$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli
                        roles: 'ROLE_USER'
                    admin:
                        password: a$cyTWeE9kpq1PjqKFiWUZFuCRPwVyAZwm4XzMZ1qPUFl7/flCM3V0G
                        roles: 'ROLE_ADMIN'

現在一切都和以前一樣。但如果你有動態使用者(如,資料庫中的),其密碼在入庫之前,你如何才能程式化的加密之呢?別擔心,參考 手動加密密碼 以了解細節。

此種方式的加密演算法能否受到支援取決於你的PHP版本,但是包括hash_algos PHP函數所傳回的演算法,以及一些其他(如bcrypt)都受到支持。參考 Security參考 中的 encoders 選項鍵作為範例。

針對不同的用戶,分別使用不同的演算法也是可能的。參考 如何動態選擇密碼加密演算法 以了解更多細節。

#

D) 配置完成! 

恭喜你!現在你有了一個可以使用的基於HTTP basic驗證、並從 security.yml 檔案中載入使用者的「驗證系統」了。

根據你的設置,尚有後續步驟:

##2) 拒絕存取, Roles和其他授權方式 

現在,使用者可以透過

http_basic 或其他方式登入你的程式。了不起呀!現在你需要學習如何拒絕存取(deny access)並且能與User物件一起工作。這被稱為authorization(授權),它的工作是決定使用者是否可以存取某些資源(如一個URL、一個model物件、一個被呼叫的方法等...)。

授權過程分為兩個不同的面向:

  1. 使用者在登入時收到一組特定的Roles(角色。如

    ROLE_ADMIN )。

  2. 你加入程式碼,以便某個資源(例如url、控制器)需要一個特定「屬性」(多數時候就是一個類似

    ROLE_ADMIN 的role)才能被訪問到。

除了roles(如

ROLE_ADMIN ),你還可以使用其他屬性/字串來(如EDIT )來保護一個資源,並且透過使用voters或Symfony的ACL系統,來令它們生效。這在你需要檢查用戶A能否「編輯」物件B(如,id為5的產品)時極為好用。參考 Access Control Lists (ACLs):保護單一資料庫物件

#

Roles/角色 

當有使用者登入時,他們會接收到一組roles(如 ROLE_ADMIN)。在上例中,這些(roles)都被寫死到 security.yml 中了。如果你從資料庫載入用戶,它們應該存在了表格的某一列。

你要分給一個使用者的所有roles,一定要以 ROLE_ 前綴開始。否則,它們不會被Symfony的Security系統以常規方式來操作(除非使用高級手段,否則你把FOO 這樣的role分給一個用戶,然後像下面這樣 去檢查FOO 是行不通的)。

roles很簡單,而且基本上都是你根據需要自行創造的字串。例如,如果你打算限制存取你網站部落格的admin部分,可以使用ROLE_BLOG_ADMIN 這個role來進行保護。 role毋須在其他地方定義-你就可以開始用它了。

確保每個使用者至少有一個 角色,否則他們會被當作未驗證之人。常見的做法就是給每個 普通用戶一個 ROLE_USER

你也可以指定role層級,此時,擁有某些roles意味著你同時擁有了其他的roles。

新增程式碼以拒絕存取 

要拒絕存取(deny access)某些東東,有兩種方式可以實現:

##透過條件匹配來保護URL(access_control) 

對程式的某一部分進行保護時的最基本方法是對URL進行完整的條件匹配。之前你已經看到,任何符合了正規表示式

^/admin 的頁面都需要ROLE_ADMIN

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...     'firewalls' => array(
        // ...
        'default' => array(
            // ...
        ),
    ),
   'access_control' => array(
       // require ROLE_ADMIN for /admin*
        array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
    ),));
XML:<!-- 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>
        <!-- ... -->         <firewall name="default">
            <!-- ... -->
        </firewall>         <!-- require ROLE_ADMIN for /admin* -->
        <rule path="^/admin" role="ROLE_ADMIN" />
    </config></srv:container>

YAML:# app/config/security.ymlsecurity:    # ...
    firewalls:        # ...
        default:            # ...
    access_control:        # require ROLE_ADMIN for /admin*
        - { path: ^/admin, roles: ROLE_ADMIN }

能夠保護全部(URL所屬的)區域固然很好,但你可能還想 保護控制器的某一action

如果需要,你可以定義任意多個URL符合條件 - 每個條件都是正規表示式。 但是,只是有一個會被比對。 Symfony會從(檔案中的)頂部開始尋找,一旦發現 access_control 中的某個入口與URL相匹配,就立即結束。

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...     'access_control' => array(
        array('path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'),
        array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
    ),));
XML:<!-- 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/users" role="ROLE_SUPER_ADMIN" />
        <rule path="^/admin" role="ROLE_ADMIN" />
    </config></srv:container>
YAML:# app/config/security.ymlsecurity:    # ...
    access_control:
        - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
        - { path: ^/admin, roles: ROLE_ADMIN }

在path中加了一個 ^ 是指,僅當URL「按照正規條件那樣起頭」時,才會匹配到。例如,一個path若只有/admin(不包含^)則會符合到/admin/foo 但同時也符合了/foo/ admin 這種。

理解access_control是如何運作的

access_control 部分異常強大,但如果你不明白它的工作原理,它也會很危險(畢竟牽涉到安全性)。 access_control 除了符合URL,還可符合IP位址、主機名稱和HTTP method。它也可以用於將使用者重定向到 https 版本的URL條件中去。

要了解這一切,參考 Security的access_control是如何運作的

保護控制器和程式碼中的其他部分 

#在控制器中你可以輕鬆謝絕存取:

// ... public function helloAction($name){
    // The second parameter is used to specify on what object the role is tested.
    // 第二个参数用于指定“要将role作用到什么对象之上”
    $this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');     // Old way / 老办法:
    // if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
    //     throw $this->createAccessDeniedException('Unable to access this page!');
    // }     // ...}

兩種情況下,一個特殊的AccessDeniedException 會被拋出,這最終觸發了Symfony內部的一個403 HTTP回應。

就是這樣!如果是尚未登入的用戶,他們會被要求登入(如,重定向到登入頁面)。如果他們已經 登錄,但不具備 ROLE_ADMIN 角色,則會被顯示403拒絕訪問頁面(此頁可以 自訂)。如果他們已登入同時擁有正確的roles,程式碼就會繼續執行。

多虧了SensioFrameworkExtraBundle,你可以在控制器中使用annotations

// ...use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; /**
 * @Security("has_role('ROLE_ADMIN')")
 */public function helloAction($name){
    // ...}

參考 FrameworkExtraBundle 以了解更多。

模式版中的存取控制 

如果你想在模版中檢查目前使用者是否具有一個role,可以使用內建的is_granted( ) helper函數:

PHP:<?php if ($view['security']->isGranted('ROLE_ADMIN')): ?>
    <a href="...">Delete</a><?php endif ?>
Twig:{% if is_granted('ROLE_ADMIN') %}
    <a href="...">Delete</a>{% endif %}

保護其他服務 

若像「保護控制器的」一樣寫一些類似程式碼,Symfony中的任何地方都可以被保護。假設你有一個服務(即一個php類別)用於發送電子郵件。你可以限制使用此類 - 不管它被用在何處 - 只有特定用戶可以使用它。

參考 如何保護程式中的服務和方法 以了解多。

檢查使用者是否已登入(IS_AUTHENTICATED_FULLY) 

目前為止,你已經檢查了基於role的訪問- 那些以ROLE_ 前綴開頭的字串,被分配給了使用者。但是,如果你 想檢查使用者是否登入(並不在乎什麼role不role的),那麼你可以使用IS_AUTHENTICATED_FULLY// ... public function helloAction ($name){

    if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
        throw $this->createAccessDeniedException();
    }     // ...}

你當然也可以使用在access_control 中使用它。

IS_AUTHENTICATED_FULLY 不是一個role,但是它的某些行為又像是role,而且每個成功登入的使用者都有這麼一個。事實上,類似的特殊屬性共有三個:

  • IS_AUTHENTICATED_REMEMBERED所有 已登入使用者都有它,哪怕他們是透過“ remember me cookie」登入進來的。就算你並沒有使用 remember me功能,你依然能夠透過這個屬性來檢查使用者是否已經登入。

  • IS_AUTHENTICATED_FULLY:類似於 IS_AUTHENTICATED_REMEMBERED ,但更健壯。那些僅憑 “remember me cookie”中的 IS_AUTHENTICATED_REMEMBERED 登入的用戶,並不會擁有 IS_AUTHENTICATED_FULLY

  • IS_AUTHENTICATED_ANONYMOUSLY#:所有 使用者(甚至是匿名使用者)都有此屬性-當把URL置於白名單 以確保能被存取時,它很有用。參考 Security的access_control是如何運作的 以了解更多。

你也可以在模版中使用表達式:

PHP:<?php if ($view['security']->isGranted(new Expression(
    '"ROLE_ADMIN" in roles or (user and user.isSuperAdmin())'))): ?>
    <a href="...">Delete</a><?php endif; ?>
Twig:{% if is_granted(expression(
    '"ROLE_ADMIN" in roles or (user and user.isSuperAdmin())')) %}
    <a href="...">Delete</a>{% endif %}

關於表達式和security性的更多細節,參考Security: 複雜的Access Controls表達式.

#Access Control Lists (ACLs):保護單一資料庫物件 

試想,你正在設計一個博客,用戶可以在主題下面發表評論。你也想讓用戶能編輯自己的評論,但其他用戶不要想。此外,身為管理員用戶,你希望能夠編輯所有 評論。

要做到這一點,你有兩個選擇:

  • Voters 允許使用者編寫自己的業務邏輯(如,使用者可以編輯這篇文章是因為他們是創建人)來檢查訪問。你可能會使用這個選擇 —— 它夠靈活,可以解決以上問題。

  • ACLs 允許你建立一個資料庫結構,在其中,你可以對任意 使用者指派針對任意對象的任意 存取權限(如,EDIT、VIEW)。要使用ACLs,你需要一個管理員用戶,以便透過某些管理介面,來對你的系統進行自訂的權限分配(grant custom access)。

兩種情況下,你仍然需要使用與前面範例類似的方法來實作deny access(拒絕存取)。

取得使用者物件 

驗證之後,就可以透過security.token_storage 服務來存取目前使用者的User#對象。在控制器內,這樣寫:

public function indexAction(){
    if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
        throw $this->createAccessDeniedException();
    }     $user = $this->getUser();     // the above is a shortcut for this / 上面的写法是通过以下取法(再进行授权)的快捷方式
    $user = $this->get('security.token_storage')->getToken()->getUser();}


用戶將是一個對象,該對象所屬的類,則取決於你的user provider


3.2透過方法簽署(method signature)來取得使用者的功能,是從Symfony 3.2開始引入的。如果你繼承了 Controller,則仍然可以透過呼叫 $this->getUser() 來取得。

現在,基於你的 User對象,可以呼叫任何方法。例如,如果User物件中有一個getFirstName() 方法,你可以使用它:

use Symfony\Component\HttpFoundation\Response;
// ... public function indexAction(){
  // ...     return new Response('Well hi there '.$user->getFirstName());}

總是登入使用者是否登入 

對使用者的初次驗證十分重要。如果未登陸,$user 就是 nullanon. 字串之一。等一下,為什麼呢?是的,這很奇怪。如果你沒有登錄,嚴格來講,用戶應該是anon.,儘管控制器中的getUser() 快捷方法會出於方便將其轉變為null。當使用UserInterface 的類型提示並且登錄(being logged in)是可選的時候,你可以為參數配置null值:

public function indexAction(UserInterface $user = null){
    // $user is null when not logged-in or anon.
    // 当用户没有登陆,或者是anno.时,$user是null}

觀點是這樣的:在使用User物件之前應該始終檢查使用者是否已登錄,可使用isGranted 方法(或access_control)來完成:

// yay! Use this to see if the user is logged in// 耶!使用这个来查看用户是否已登陆if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
    throw $this->createAccessDeniedException();} // boo :(. Never check for the User object to see if they're logged in// 哄!:(. 切勿通过检查User对象来判断用户是否已登陆if ($this->getUser()) { }

在模版中取得使用者 

物件在twig模版中可以使用app.user鍵:

PHP:<?php if ($view['security']->isGranted('IS_AUTHENTICATED_FULLY')): ?>
    <p>Username: <?php echo $app->getUser()->getUsername() ?></p><?php endif; ?>
Twig:{% if is_granted('IS_AUTHENTICATED_FULLY') %}
    <p>Username: {{ app.user.username }}</p>{% endif %}

Logging out/登出登入 

注意,當使用http-basic驗證方式的防火牆時,是沒有辦法退出的:log out 的唯一方式就是令瀏覽器停止在每次請求中發送你的使用者名稱和密碼。清除瀏覽器快取或重啟它,往往有用。某些web開發工具(譯註:可能是指瀏覽器的f12)也可能有用。

通常情況下,你希望使用者能夠登出。幸運的是,當你啟動logout 設定參數後,防火牆可以幫助你自動處理:

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...     'firewalls' => array(
        'secured_area' => array(
            // ...
            'logout' => array('path' => '/logout', 'target' => '/'),
        ),
    ),));
Twig:<!-- 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>
        <!-- ... -->         <firewall name="secured_area">
            <!-- ... -->
            <logout path="/logout" target="/" />
        </firewall>
    </config></srv:container>
YAML:# app/config/security.ymlsecurity:    # ...
    firewalls:
        secured_area:            # ...
            logout:
                path:   /logout
                target: /

接下來,你需要去給這個URL建立一個路由(但不需要控制器):

PHP:// app/config/routing.phpuse Symfony\Component\Routing\RouteCollection;use Symfony\Component\Routing\Route; $collection = new RouteCollection();$collection->add('logout', new Route('/logout')); return $collection;
XML:<!-- app/config/routing.xml --><?xml version="1.0" encoding="UTF-8" ?><routes xmlns="http://Symfony.com/schema/routing"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://Symfony.com/schema/routing        http://Symfony.com/schema/routing/routing-1.0.xsd">     <route id="logout" path="/logout" /></routes>
YAML:# app/config/routing.ymllogout:
    path: /logout
###

就是這樣!透過把使用者傳送到 /logout (或你任意設定的 path 選項),Symfony將取消目前使用者的驗證資訊。

一旦使用者被註銷,他們會被重定向到已經定義的 target 參數所對應的路徑中(如 homepage)。

如果你需要在登出後做一些更有趣的事,可以透過加入success_handler 鍵來指定一個「登出成功控制器」(logout success handler ),將它填入一個服務定義之id,該服務的class必須實作LogoutSuccessHandlerInterface介面。參考 Security Configuration Reference

按等級劃分的Roles 

#並非把大量roles統統關聯到用戶,透過創建role hierarchy(角色層級),你可以定義一套「角色繼承規則」:

PHP:// app/config/security.php$container->loadFromExtension('security', array(
    // ...     'role_hierarchy' => array(
        'ROLE_ADMIN'       => 'ROLE_USER',
        'ROLE_SUPER_ADMIN' => array(
            'ROLE_ADMIN',
            'ROLE_ALLOWED_TO_SWITCH',
        ),
    ),));
XML:<!-- 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>
        <!-- ... -->         <role id="ROLE_ADMIN">ROLE_USER</role>
        <role id="ROLE_SUPER_ADMIN">ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH</role>
    </config></srv:container>
YAML:# app/config/security.ymlsecurity:    # ...
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

在上面的設定中,使用者ROLE_ADMIN 也會具備ROLE_USER role。 ROLE_SUPER_ADMIN role,同時擁有 ROLE_ADMINROLE_ALLOWED_TO_SWITCH 以及 ROLE_USER(繼承自 ROLE_ADMIN)。

總結 

喔~幹得好!你已經了解到比security基礎更多的內容。最困難的部分就是當你有自訂需求時:像是定義一個驗證策略(如,api tokens),複雜的授權邏輯以及許多其他事情(因為security本來就很複雜!)。

幸運的是:這裡有很多的文章,意在述清各種狀況。同時,參考 Security參考。許多配置選項並無細節,然而當看到完整的配置樹時,仍會有一定幫助。

祝好運!

    1
    $  php bin/console security:encode-password