注意:如果你需要為存儲在某種數據庫中的用戶做一個登錄表單,那么你應該考慮使用FOSUserBundle,這有助於你建立你的User對象,還為您提供了常見的登錄、注冊、忘記密碼的路由和控制器。
在此文章中,將構建一個傳統的登錄表單。當然,當用戶登錄時,你可以從數據庫或者任何地方加載用戶。
首先,啟用防火牆下表單登錄
# app/config/security.yml security: # ... firewalls: default: anonymous: ~ http_basic: ~ form_login: login_path: /login check_path: /login_check
這個login_path和check_path也可以是路由名稱(但不能有強制通配符例如 /login/{foo})這里foo沒有默認值
現在,當安全系統啟動認證過程,它會讓用戶跳轉到登錄表單 /login。你的工作是實現這個登錄表單視覺。首先,創建一個新的SecurityController在bundle中:
// src/AppBundle/Controller/SecurityController.php namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; class SecurityController extends Controller { }
下一步創建兩個路由:分別是剛才form_login下的設置的兩個路徑(/login和/login_check):
annotations
// src/AppBundle/Controller/SecurityController.php // ... use Symfony\Component\HttpFoundation\Request; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class SecurityController extends Controller { /** * @Route("/login", name="login_route") */ public function loginAction(Request $request) { } /** * @Route("/login_check", name="login_check") */ public function loginCheckAction() { // this controller will not be executed, // as the route is handled by the Security system } }
非常好!下一步,你要去添加loginAction的邏輯,並把需要的渲染到login表單:
// src/AppBundle/Controller/SecurityController.php public function loginAction(Request $request) { $authenticationUtils = $this->get('security.authentication_utils'); // get the login error if there is one $error = $authenticationUtils->getLastAuthenticationError(); // last username entered by the user $lastUsername = $authenticationUtils->getLastUsername(); return $this->render( 'security/login.html.twig', array( // last username entered by the user 'last_username' => $lastUsername, 'error' => $error, ) ); }
這個security.authentication_utils服務和
AuthenticationUtils類在symfony2.6都有介紹。
不要讓這個控制器迷惑你。你看到當用戶提交表單的這一刻,security系統會自動處理表單提交到這個控制器。如果用戶提交了一個無效的用戶名和密碼,該控制器可以從security系統中讀出表單提交的錯誤,以便把他顯示給用戶。
換句話說,你的工作是顯示登錄表單和任何可能發生的登錄錯誤,但是security系統本身負責檢查提交的用戶名和密碼並認證用戶。
最后,創建模版:
{# app/Resources/views/security/login.html.twig #} {# ... you will probably extends your base template, like base.html.twig #} {% if error %} <div>{{ error.messageKey|trans(error.messageData) }}</div> {% endif %} <form action="{{ path('login_check') }}" method="post"> <label for="username">Username:</label> <input type="text" id="username" name="_username" value="{{ last_username }}" /> <label for="password">Password:</label> <input type="password" id="password" name="_password" /> {# If you want to control the URL the user is redirected to on success (more details below) <input type="hidden" name="_target_path" value="/account" /> #} <button type="submit">login</button> </form>
這個傳入到模版的錯誤變量是一個AuthenticationException。它包含很多信息-一些敏感信息-關於認證失敗信息,所以你要明智的使用它。
要實現這些東西,要注意這幾個要求:
- 該表單必須提交到/login_check,因為你在security.yml的form_login健中配置的
- 這個用戶名一定要name為_username並且password一定要name為_password。
其實所有的這些都可以配置到form_login健下,請查看 form登錄配置。
此登錄表單目前沒有使用CSRF攻擊。如果需要請閱讀 Using CSRF Protection in the Login Form 。
就是這樣!當您提交表單,security系統會自動檢查用戶的憑證,驗證用戶或向用戶發送錯誤信息在登陸表單。
回顧整個過程:
1.用戶試圖訪問被保護的資源。
2.防火期啟用認證過程將用戶重定向到登錄表單(/login)
3.這個例子中,通過路由(route)和控制器(controller)來顯示登錄表單。
4.用戶提交登錄表單到 /login_check;
5.security系統截取請求,檢查用戶提交的憑據,驗證他們是否正確,如果他不是系統允許的,頁面會重新跳轉到表單。
成功后,重定向
如果提交的憑證是正確的,該用戶會被重新定向到請求的原始頁面(如/admin/foo)。如果用戶最初直接進入登錄頁面,它需要跳轉到首頁。這些都是可以設定的,並且允許你指定到一個指定的url上。
更多細節,請參閱 How to Customize your Form Login。
避免常見問錯誤
在設置表單時應該注意一些常見的陷阱。
1.創建正確的路由
首先,確保你已經定義了正確的/login和/login_check,他們應該和你配置的login_path和check_path是一樣的。 如果你配置錯誤他會重定向到一個404頁面,而不是登錄頁面,或者會出現提交表單不執行任何操作(你會一遍又一遍的看到登錄表單)。
2.確保登錄頁面可以訪問
此外,要確保登錄頁面匿名用戶可以訪問。例如,下面的配置-ROLE_ADMIN角色用於所有的URL(包括 /login),將會出現重定向循環:
# app/config/security.yml # ... access_control: - { path: ^/, roles: ROLE_ADMIN }
添加access_control中添加一個 /login/*,並且指定一個任何身份都可以進入的角色。
# app/config/security.yml # ... access_control: - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/, roles: ROLE_ADMIN }
另外,如果您的防火牆沒有允許匿名用戶(沒有anonymous
鍵),你需要去創建一個特殊的防火牆,允許匿名用戶登錄:
# app/config/security.yml # ... firewalls: # order matters! This must be before the ^/ firewall login_firewall: pattern: ^/login$ anonymous: ~ secured_area: pattern: ^/ form_login: ~
3.確保 /login_check 位於防火牆的后面
確保你的check_path的url(如 /login_check)在表單登錄防火牆里(例如本例,單一的防火牆匹配所有URL,包含/login_check)。如果/login_check不匹配路由,你會收到一個Unable to find the controller for path “/login_check”的異常。
4.多個防火牆不共享相同的Security內容
如果你使用多個防火牆並且你進行認證了一個防火牆,其他的防火牆就不會對此做自動認證了。不同的防火牆,就像不同的安全系統。要做到這一點,你就必須要明確指定不同防火牆下相同的 Firewall Context 。但大多數應用,有一個主要的防火牆就足夠了。
5.路由錯誤不受防火牆限制
Security已經把路由里的404頁面設置成了不受防火牆限制。這意味着在這些頁面上,你不能檢測Security甚至訪問用戶對象。請查看 How to Customize Error Pages