支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
如果你需要一个登录表单,并且把用户存到某种类型的数据库中,那么你应该考虑使用 FOSUserBundle,它帮助你建立 User
对象,还提供了常见任务所需的的路由和控制器,包括登录、注册、找回密码等。
在本文中,你将构建一个传统的登录表单。当然,在用户登录时,你可以从任何地方加载他们 - 比如数据库。参考 配置“如何加载用户”。
首先,在firewall中开启“表单登录”:
1 2 3 4 5 6 7 8 9 10 | # app/config/security.yml
security:
# ...
firewalls:
main:
anonymous: ~
form_login:
login_path: login
check_path: login |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:srv="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<config>
<firewall name="main">
<anonymous />
<form-login login-path="login" check-path="login" />
</firewall>
</config>
</srv:container> |
login_path
和 check_path
也可以是路由名称(但不能有任何通配符 - 如 /login/{foo}
而 foo
并没有默认值)。
现在,当安全系统初始化认证过程时,它会把用户重定向到登录表单 /login
。实现这个登录表单是你要做的事。首先,在bundle中创建一个新的 SecurityController
:
1 2 3 4 5 6 7 8 | // src/AppBundle/Controller/SecurityController.php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class SecurityController extends Controller
{
} |
接下来,配置路由,也就是之前你用在 form_login
配置中的(login
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // src/AppBundle/Controller/SecurityController.php
// ...
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class SecurityController extends Controller
{
/**
* @Route("/login", name="login")
*/
public function loginAction(Request $request)
{
}
} |
1 2 3 4 | # app/config/routing.yml
login:
path: /login
defaults: { _controller: AppBundle:Security:login } |
1 2 3 4 5 6 7 8 9 10 11 | <!-- 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="login" path="/login">
<default key="_controller">AppBundle:Security:login</default>
</route>
</routes> |
1 2 3 4 5 6 7 8 9 10 | // app/config/routing.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
$collection = new RouteCollection();
$collection->add('login', new Route('/login', array(
'_controller' => 'AppBundle:Security:login',
)));
return $collection; |
很好!下一步,添加 loginAction
的逻辑以显示登录表单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // 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 / 获取用户输入的username(用户名)
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'security/login.html.twig',
array(
// last username entered by the user
'last_username' => $lastUsername,
'error' => $error,
)
);
} |
别被这个控制器迷惑。你即将看到,当用户提交表单时,security系统会自动帮你处理表单提交。如果用户提交了无效的用户名和密码,该控制器会从security系统中读取表单提交的错误,再显示给用户。
换句话说,你的任务是显示 登录表单以及任何可能发生的登录错误(login error),但是安全系统本身会负责检查“已提交的用户名和密码”并对用户进行认证。
最后,创建模版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | {# app/Resources/views/security/login.html.twig #}
{# ... you will probably extends your base template, like base.html.twig #}
{# ... 这里你也许会继承基础布局模板,如 base.html.twig #}
{% if error %}
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<form action="{{ path('login') }}" 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)
如果你要控制“用户成功登录后被重定向的URL”的话(下文有更多细节)
<input type="hidden" name="_target_path" value="/account" />
#}
<button type="submit">login</button>
</form> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <!-- src/AppBundle/Resources/views/Security/login.html.php -->
<?php if ($error): ?>
<div><?php echo $error->getMessage() ?></div>
<?php endif ?>
<form action="<?php echo $view['router']->path('login') ?>" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="_username" value="<?php echo $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> |
传入到模版的 error
变量是 AuthenticationException 的实例。它可以包含很多信息- 甚至是敏感信息 - 即关于认证失败的,要明智地使用之。
表单看上去可以千奇百怪,但它通常遵循如下约定:
form
元素发送一个 POST
请求到 login
路由,因为你在 security.yml
的 form_login
键中是这样配置的;
用户名字段的name必须是 _username
而密码字段的name则必须是 _password
。
其实所有这些都可以在 form_login
键下进行配置,参考 表单登录配置。
此登录表单目前不能对CSRF攻击进行防护。阅读 使用CSRF保护登录表单 以了解如何保护表单。
妥了!当你提交表单时,security系统会自动检查用户的凭证,要么通过认证,要么把用户送回“显示有错误信息”的登陆表单。
回顾整个过程:
用户试图访问受保护的资源;
防火墙通过“将用户重定向到登录表单(/login
)”启用认证进程;
本例中,通过路由(route)和控制器(controller)来渲染出(译注:render,即输出之意)/login
页面的登录表单;
用户提交表单到 /login
;
security系统拦截请求,检查用户提交的凭据,若正确则认证之,如果不正确,用户会被送回登录表单。
如果提交的凭证是正确的,用户会被重新定向到其所请求的原始页面(如 /admin/foo
)。如果用户最初直接进入的登录页面,他们会被重定向到首页(homepage)。这些都是可以设定的,允许你,比如,将用户重定向到一个指定的url上。
关于此点的更多细节,以及如何从大体上自定义表单登录进程,请参考 如何自定义你的表单登录。
在设置表单时应注意一些常见的陷阱。
首先,确保你定义了正确的 /login
路由,它和你的 login_path
和 check_path
的配置值是一样的。如果配置错误,有可能会导致重定向到一个404页面而不是登录页面,或者在表单提交后不执行任何操作(你会一遍又一遍的看到登录表单)。
此外,要确保登录页面可以被匿名用户访问。例如,下面的配置 - 所有URLs皆需要 ROLE_ADMIN
role(包括 /login
URL),将引发重定向循环:
1 2 3 4 5 | # app/config/security.yml
# ...
access_control:
- { path: ^/, roles: ROLE_ADMIN } |
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!-- 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="^/" role="ROLE_ADMIN" />
</config>
</srv:container> |
向access_control中添加一个 /login/*
,即毋须认证身份而修复问题:
1 2 3 4 5 6 | # app/config/security.yml
# ...
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_ADMIN } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <!-- 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="^/login" role="IS_AUTHENTICATED_ANONYMOUSLY" />
<rule path="^/" role="ROLE_ADMIN" />
</config>
</srv:container> |
接着,要确保你的 check_path
URL (如 /login
) 处在表单登录的防火墙之内(本例中,单一防火墙匹配了所有 URL,包括 /login
)。如果 /login
无法匹配到任何防火墙,你会收到一个 Unable to find the controller for path “/login”
异常。
如果你使用多个防火墙并对其中的一个开展认证,其他的防火墙就不会 自动对你进行认证了。不同的防火墙,就像不同的安全系统。要实现这一点,你必须对不同的防火墙显式指定相同的 Firewall Context。然而一般来说,有一个主力防火墙就足够了。
由于routing是在security之前 完成,404页不受防火墙限制。这意味着在这些页面你无法检查安全性,甚至访问到user对象。参考 如何自定义错误页。
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。