 
                    支付宝扫一扫付款
 
                    微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
如果你需要一个登录表单,并且把用户存到某种类型的数据库中,那么你应该考虑使用 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> | 
另外,如果您的防火墙没有允许匿名用户(没有anonymous 键),你需要去创建一个特殊的防火墙,允许匿名用户登录:
| 1 2 3 4 5 6 7 8 9 10 11 | # app/config/security.yml
 
# ...
firewalls:
    # order matters! This must be before the ^/ firewall
    login_firewall:
        pattern:   ^/login$
        anonymous: ~
    secured_area:
        pattern:    ^/
        form_login: ~ | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <!-- 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="login_firewall" pattern="^/login$">
            <anonymous />
        </firewall>
 
        <firewall name="secured_area" pattern="^/">
            <form-login />
        </firewall>
    </config>
</srv:container> | 
确保你的check_path的url(如 /login)在表单登录防火墙里(例如本例,防火墙匹配所有URL里,包含/login)。如果/login没有匹配任何防火墙,你会收到一个Unable to find the controller for path “/login”的异常。
如果你使用多个防火墙并且你进行认证了一个防火墙,其他的防火墙就不会对此做自动认证了。不同的防火墙,就像不同的安全系统。如果你想实现这一点,你就必须要明确指定不同防火墙下相同的 Firewall Context 。但大多数应用,有一个主要的防火墙就足够了。
Security已经把路由里的404页面设置成了不受防火墙限制。这意味着在这些页面上不用检测,甚至访问这些页面上的用户对象。请查看 如何定义错误页。
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。