安全(Security)
Symfony的安全系統(tǒng)(security system)是非常強大的,但在設置它時也可能令人迷惑。在本大章中,你將學會如何一步步地設置程序的security,從配置防火墻(firewall)以及加載用戶,到拒絕訪問和取到用戶對象。根據(jù)你的需求,有時在進行初始化設置時是痛苦的。然而一旦(配置)完成,Symfony的security系統(tǒng)在用起來時,則是既靈活又(希望能夠)有樂趣。
由于有很多話要說,本章按幾個大部頭來組織:
初始化
security.yml
的設置 (authentication/驗證 );拒絕訪問你的程序 (authorization/授權 );
獲取當前的User對象;
它們被細分為許多小塊內(nèi)容(但仍然令人著迷),像是 logging out 和 加密用戶密碼。
1)初始化security.yml的設置 (Authentication/驗證) ?
security系統(tǒng)在 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開發(fā)工具 - 也就是居于 /_profiler
和 /_wdt
之下的那些URL將不會被你的security所阻止。
你也可以針對請求中的其他細節(jié)來匹配一個請求(如 host主機)。閱讀 如何把防火墻限制在特定請求 以了解更多內(nèi)容和例程。
所有其他的URL將被 default
防火墻中處理(沒有 pattern
鍵意味著它可以匹配所有 URL)。你可以認為防火墻就是你的security系統(tǒng),因此通常只有一個主力防火墻就變得有意義了。但這并不 意味著每一個URL都需要驗證 - anonymous
鍵可以搞定這個。事實上,如果你現(xiàn)在去首頁,是可以訪問的,并將看到你以 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
下創(chuàng)建一個新頁面 。例如,如果你使用annotations,創(chuàng)建下例代碼:// 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和其他授權 小節(jié)中的“拒絕訪問”等更多內(nèi)容。
太好了,如果你去訪問 /admin
,會看到HTTP Basic驗證的彈出式登錄:
但是,你是以何種身份登錄進來?用戶(信息)又來自哪里呢?
想要使用一個傳統(tǒng)的form表單?很好!參考 如何建立一個傳統(tǒng)的登錄表單。還支持其他什么(驗證)方式?參考 Configuration Reference 或者 構建你自己的。
如果你的程序是通過諸如Google,F(xiàn)acebook或者Twitter等三方服務來完成登錄,參閱 HWIOAuthBundle 社區(qū)bundle。
B) 配置“如何加載用戶” ?
當你在輸入用戶名時,Symfony就要從某個地方加載用戶的信息。這就是所謂的“user provider”,由你來負責配置它。Symfony有一個內(nèi)置方式來 從數(shù)據(jù)庫中加載用戶,但也可以 創(chuàng)建你自己的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設置的全部細節(jié)。
試著以用戶名 admin
和密碼 kitten
來登錄。你應該看到一個錯誤!
No encoder has been configured for account "Symfony\Component\Security\Core\User\User"
要修復此問題 ,添加一個 encoders
?。?/p>
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
對象中。如果你從數(shù)據(jù)庫中加載用戶 或者 從其他來源加載,你需要使用自定義的User類。但當你使用“in memory” provider時,它直接給了你一個 Symfony\Component\Security\Core\User\User
對象。
無論你使用什么樣的User類,你都需要去告訴Symfony它們的密碼加密算法是什么。在本例中,密碼只是明文的文本,但很快,你要把它改成 bcrypt
。
如果你現(xiàn)在刷新,你就會登錄進來!web除錯工具欄會告訴你,你是何人,你的roles(角色)是什么:
由于此URL需要的是 ROLE_ADMIN
,如果你登錄的是 ryan
用戶,將被拒絕訪問。更多內(nèi)容參考后面的 [URL受到保護的條件(access_control](#catalog9)。
從數(shù)據(jù)庫加載用戶 ?
如果你想通過Doctrine Orm加載用戶,很容易!參考 如何從數(shù)據(jù)庫中(Entity Provider)加載Security系統(tǒng)之用戶 以了解全部細節(jié)。
C) 對用戶密碼進行加密 ?
不管用戶是存儲在 security.yml
里,數(shù)據(jù)庫里還是其他地方,你都需要去加密其密碼??坝玫淖詈盟惴ㄊ?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
當然,用戶密碼現(xiàn)在需要被這個指定算法加密。對于寫死(在config.yml中)的用戶,你可以用內(nèi)置命令來完成:
1 | $ php bin/console security:encode-password |
它將帶給你下面這樣(密碼被加密)的東東:
PHP:// app/config/security.php$container->loadFromExtension('security', array( // ... 'providers' => array( 'in_memory' => array( 'memory' => array( 'users' => array( 'ryan' => array( 'password' => '$2a$12$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli', 'roles' => 'ROLE_USER', ), 'admin' => array( 'password' => '$2a$12$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="$2a$12$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli" roles="ROLE_USER" /> <user name="admin" password="$2a$12$cyTWeE9kpq1PjqKFiWUZFuCRPwVyAZwm4XzMZ1qPUFl7/flCM3V0G" roles="ROLE_ADMIN" /> </memory> </provider> </config></srv:container>
YAML:# app/config/security.ymlsecurity: # ... providers: in_memory: memory: users: ryan: password: $2a$12$LCY0MefVIEc3TYPHV9SNnuzOfyr2p/AXIGoQJEDs4am4JwhNz/jli roles: 'ROLE_USER' admin: password: $2a$12$cyTWeE9kpq1PjqKFiWUZFuCRPwVyAZwm4XzMZ1qPUFl7/flCM3V0G roles: 'ROLE_ADMIN'
現(xiàn)在一切都和以前一樣。但如果你有動態(tài)用戶(如,數(shù)據(jù)庫中的),其密碼在入庫之前,你如何才能程序化的加密之呢?別擔心,參考 手動加密密碼 以了解細節(jié)。
此種方式的加密算法能否受到支持取決于你的PHP版本,但是包括 hash_algos PHP函數(shù)所返回的算法,以及一些其他(如 bcrypt
)都受到支持。參考 Security參考 中的 encoders
選項鍵作為示例。
針對不同的用戶,分別使用不同的算法也是可能的。參考 如何動態(tài)選擇密碼加密算法 以了解更多細節(jié)。
D) 配置完成! ?
祝賀你!現(xiàn)在你有了一個可以使用的基于HTTP basic驗證、并從 security.yml
文件中加載用戶的“驗證系統(tǒng)”了。
根據(jù)你的設置,尚有后續(xù)步驟:
從不同來源來加載用戶,例如 數(shù)據(jù)庫 或者 其他源頭;
在 授權 小節(jié)了解如何拒絕訪問(deny access)、加載用戶對象,以及操作Roles;
2) 拒絕訪問,Roles和其他授權方式 ?
現(xiàn)在,用戶可以通過 http_basic
或者其他方式來登錄你的程序。了不起呀!現(xiàn)在你需要學習如何拒絕訪問(deny access)并且能與User對象一起工作。這被稱為authorization(授權),它的工作是決定用戶是否可以訪問某些資源(如一個URL、一個model對象、一個被調(diào)用的方法等...)。
授權過程分為兩個不同的方面:
用戶在登錄時收到一組特定的Roles(角色。如
ROLE_ADMIN
)。你添加代碼,以便某個資源(例如url、控制器)需要一個特定“屬性”(多數(shù)時候就是一個類似
ROLE_ADMIN
的role)才能被訪問到。
除了roles(如 ROLE_ADMIN
),你還可以使用其他屬性/字符串來(如 EDIT
)來保護一個資源,并且通過使用voters或Symfony的ACL系統(tǒng),來令它們生效。這在你需要檢查用戶A能否“編輯”對象B(如,id為5的產(chǎn)品)時極為好用。參考 Access Control Lists (ACLs):保護單個數(shù)據(jù)庫對象。
Roles/角色 ?
當有用戶登錄時,他們會接收到一組roles(如 ROLE_ADMIN
)。在上例中,這些(roles)都被寫死到 security.yml
中了。如果你從數(shù)據(jù)庫中加載用戶,它們應該存在了表的某一列中。
你要分給一個用戶的所有roles,一定要以 ROLE_
前綴開始。否則,它們不會被Symfony的Security系統(tǒng)按常規(guī)方式來操作(除非使用高級手段,否則你把 FOO
這樣的role分給一個用戶,然后像 下面這樣 去檢查 FOO
是行不通的)。
roles很簡單,而且基本上都是你根據(jù)需要自行創(chuàng)造的字符串。例如,如果你打算限制訪問你網(wǎng)站博客的admin部分,可以使用ROLE_BLOG_ADMIN
這個role來進行保護。role毋須在其他地方定義-你就可以開始用它了。
確保每個用戶至少有一個 角色,否則他們會被當作未驗證之人。常見的做法就是給每個 普通用戶一個 ROLE_USER
。
你還可以指定role層級,此時,擁有某些roles意味著你同時擁有了其他的roles。
添加代碼以拒絕訪問 ?
要拒絕訪問(deny access)某些東東,有兩種方式可以實現(xiàn):
使用security.yml中的access_control,即可使用URL的條件保護(如
/admin/*
)。這雖然容易,但靈活性不足;
通過條件匹配來保護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所屬的)區(qū)域固然很好,但你可能還想 保護控制器的某一action。
如果需要,你可以定義任意多個URL匹配條件 - 每個條件都是正則表達式。但是,僅僅有一個會被匹配。Symfony會從(文件中的)頂部開始尋找,一旦發(fā)現(xiàn) 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
這種。
保護控制器和代碼中的其他部分 ?
在控制器中你可以輕松謝絕訪問:
// ... public function helloAction($name){ // The second parameter is used to specify on what object the role is tested. // 第二個參數(shù)用于指定“要將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
會被拋出,這最終觸發(fā)了Symfony內(nèi)部的一個403 HTTP響應。
就是這樣!如果是尚未登錄的用戶,他們會被要求登錄(如,重定向到登錄頁面)。如果他們已經(jīng) 登錄,但不具備 ROLE_ADMIN
角色,則會被顯示403拒絕訪問頁面(此頁可以 自定義)。如果他們已登錄同時擁有正確的roles,代碼就會繼續(xù)執(zhí)行。
多虧了SensioFrameworkExtraBundle,你可以在控制器中使用annotations
// ...use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; /** * @Security("has_role('ROLE_ADMIN')") */public function helloAction($name){ // ...}
參考 FrameworkExtraBundle 以了解更多。
模版中的訪問控制 ?
如果你想在模版中檢查當前用戶是否具有一個role,可以使用內(nèi)置的 is_granted()
helper函數(shù):
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類)用于發(fā)送電子郵件。你可以限制使用此類 - 不管它被用在何處 - 只有特定用戶可以使用它。
參考 如何保護程序中的服務和方法 以了解多。
檢查用戶是否已登錄(IS_AUTHENTICATED_FULLY) ?
目前為止,你已經(jīng)檢查了基于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功能,你依然能夠通過這個屬性來檢查用戶是否已經(jīng)登錄。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性的更多細節(jié),參考 Security: 復雜的Access Controls表達式.
Access Control Lists (ACLs):保護單個數(shù)據(jù)庫對象 ?
試想,你正在設計一個博客,用戶可以在主題下面發(fā)表評論。你還想讓用戶能編輯自己的評論,但其他用戶不要想。此外,作為管理員用戶,你希望能夠編輯所有 評論。
要做到這一點,你有兩個選擇:
Voters 允許用戶編寫自己的業(yè)務邏輯(如,用戶可以編輯這篇文章是因為他們是創(chuàng)建人)來檢查訪問。你可能會使用這個選擇 - 它足夠靈活,可以解決以上問題。
ACLs 允許你創(chuàng)建一個數(shù)據(jù)庫結構,在其中,你可以對任意 用戶分配針對任意對象的任意 訪問權限(如,EDIT、VIEW)。要使用ACLs,你需要一個管理員用戶,以便通過某些管理界面,來對你的系統(tǒng)進行自定義的權限分配(grant custom access)。
兩種情況下,你仍然需要使用與前面例子中相類似的方法來實施deny access(拒絕訪問)。
獲取用戶對象 ?
驗證之后,就可以通過 security.token_storage
服務來訪問當前用戶的 User
對象。在控制器內(nèi),這樣寫:
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
,則仍然可以通過調(diào)用 $this->getUser()
來獲取。
現(xiàn)在,基于你的 User對象,可以調(diào)用任何方法。例如,如果User對象中有一個 getFirstName()
方法,你可以使用它:
use Symfony\Component\HttpFoundation\Response; // ... public function indexAction(){ // ... return new Response('Well hi there '.$user->getFirstName());}
始終檢測用戶是否登錄 ?
對用戶的初次驗證十分重要。如果未登陸,$user
就是 null
或者 anon.
字符串之一。等一下,為什么呢?是的,這很奇怪。如果你沒有登錄,嚴格來講,用戶應該是 anon.
,盡管控制器中的 getUser()
快捷方法會出于方便將其轉變?yōu)?null
。當使用 UserInterface
的類型提示并且登入(being logged in)是可選的時候,你可以為參數(shù)配置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 的唯一方式就是令瀏覽器停止在每次請求中發(fā)送你的用戶名和密碼。清除瀏覽器緩存或重啟它,往往有用。某些web開發(fā)工具(譯注:可能是指瀏覽器的f12)也可能有用。
通常情況下,你希望用戶能夠注銷。幸運的是,當你激活 logout
配置參數(shù)后,防火墻可以幫助你自動處理:
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創(chuàng)建一個路由(但不需要控制器):
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
就是這樣!通過把用戶發(fā)送到 /logout
(或者你任意配置的 path
選項),Symfony將取消當前用戶的驗證信息。
一旦用戶被注銷,他們會被重定向到已經(jīng)定義的 target
參數(shù)所對應的路徑中(如 homepage
)。
如果你需要在注銷后做一些更有趣的事,可以通過添加 success_handler
鍵來指定一個“登出成功控制器”(logout success handler),將它填寫成一個服務定義之id,該服務的class必須實現(xiàn) LogoutSuccessHandlerInterface接口。參考 Security Configuration Reference。
按等級劃分的Roles ?
并非把大量roles統(tǒng)統(tǒng)關聯(lián)到用戶,通過創(chuàng)建role hierarchy(角色層級),你可以定義一套“角色繼承規(guī)則”:
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_ADMIN
,ROLE_ALLOWED_TO_SWITCH
以及 ROLE_USER
(繼承自 ROLE_ADMIN
)。
總結 ?
喔~干得好!你已經(jīng)了解到比security基礎更多的內(nèi)容。最難的部分就是當你有自定義需求時:像是定義一個驗證策略(如,api tokens),復雜的授權邏輯以及許多其他事情(因為security本來就很復雜!)。
幸運的是:在這里有很多的文章,意在述清各種狀況。同時,參考 Security參考。許多配置選項并無細節(jié),然而當看到完整的配置樹時,仍會有一定幫助。
祝好運!