跳到内容

如何添加“记住我”登录功能

编辑此页

一旦用户通过身份验证,他们的凭据通常会存储在会话中。这意味着当会话结束时,他们将被注销,并且下次希望访问应用程序时必须再次提供登录详细信息。您可以使用带有 remember_me 防火墙选项的 Cookie,允许用户选择保持登录状态的时间比会话持续时间更长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# config/packages/security.yaml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                secret: '%kernel.secret%'
                lifetime: 604800 # 1 week in seconds
                # by default, the feature is enabled by checking a
                # checkbox in the login form (see below), uncomment the
                # following line to always enable it.
                #always_remember_me: true

7.2

从 Symfony 7.2 开始,不再需要 secret 选项。默认情况下,使用 %kernel.secret%,它使用 APP_SECRET 环境变量定义。

在配置中启用 remember_me 系统后,还有几件事要做才能使记住我功能正常工作

  1. 添加一个选择加入复选框以激活记住我;
  2. 使用支持记住我的认证器;
  3. 可选地,配置记住我 Cookie 的存储和验证方式

完成这些操作后,记住我 Cookie 将在成功验证身份后创建。对于某些页面/操作,您可以强制用户完全验证身份(即,不是通过记住我 Cookie 验证),以获得更好的安全性。

注意

remember_me 设置包含许多用于配置此功能创建的 Cookie 的设置。有关这些设置的完整描述,请参阅自定义记住我 Cookie

激活记住我系统

使用记住我 Cookie 并非总是合适的(例如,您不应在共享 PC 上使用它)。这就是为什么默认情况下,Symfony 要求您的用户通过请求参数选择加入记住我系统的原因。

表单登录的记住我

此请求参数通常通过登录表单中的复选框设置。此复选框的名称必须为 _remember_me

1
2
3
4
5
6
7
8
9
10
11
{# templates/security/login.html.twig #}
<form method="post">
    {# ... your form fields #}

    <label>
        <input type="checkbox" name="_remember_me" checked>
        Keep me logged in
    </label>

    {# ... #}
</form>

注意

或者,您可以使用 remember_me 部分下的 name 设置为此复选框配置自定义名称。

JSON 登录的记住我

如果您通过使用JSON 登录的 API 实现登录,则可以将 _remember_me 键添加到 POST 请求的正文中。

1
2
3
4
5
{
    "username": "[email protected]",
    "password": "MyPassword",
    "_remember_me": true
}

注意

或者,您可以使用防火墙的 remember_me 部分下的 name 设置为此键配置自定义名称。

始终激活记住我

有时,您可能希望始终激活记住我系统,而不允许用户选择退出。在这些情况下,您可以使用 always_remember_me 设置

1
2
3
4
5
6
7
8
9
10
# config/packages/security.yaml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                # ...
                always_remember_me: true

现在,不会检查任何请求参数,并且每次成功的身份验证都会生成一个记住我 Cookie。

向认证器添加记住我支持

并非所有身份验证方法都支持记住我(例如,HTTP Basic 身份验证不支持)。认证器使用 安全通行证上的 RememberMeBadge 来指示支持。

登录后,您可以使用安全分析器来查看此徽章是否存在

The Security page of the Symfony profiler, with the "Authenticators" tab showing the remember me badge in the passport object.

如果没有此徽章,则记住我功能将不会被激活(无论所有其他设置如何)。

向自定义认证器添加记住我支持

当您使用自定义认证器时,必须手动添加 RememberMeBadge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Service/LoginAuthenticator.php
namespace App\Service;

// ...
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;

class LoginAuthenticator extends AbstractAuthenticator
{
    public function authenticate(Request $request): Passport
    {
        // ...

        return new Passport(
            new UserBadge(...),
            new PasswordCredentials(...),
            [
                new RememberMeBadge(),
            ]
        );
    }
}

自定义记住我令牌的存储方式

记住我 Cookie 包含一个令牌,该令牌用于验证用户身份。由于这些令牌是长期存在的,因此采取预防措施以使任何生成的令牌失效非常重要。

Symfony 提供了两种验证记住我令牌的方法

基于签名的令牌
默认情况下,记住我 Cookie 包含基于用户属性的签名。如果属性发生更改,则签名也会更改,并且已生成的令牌不再被视为有效。有关更多信息,请参阅如何使用它们
持久令牌
持久令牌存储任何生成的令牌(例如,在数据库中)。这允许您通过更改数据库中的行来使令牌失效。有关如何存储令牌的更多信息,请参阅如何存储令牌

注意

您还可以通过创建一个扩展 AbstractRememberMeHandler(或实现 RememberMeHandlerInterface)的类来编写自己的自定义记住我处理程序。然后,您可以通过在 remember_me 下的 service 选项中配置服务 ID 来配置此自定义处理程序。

使用签名记住我令牌

默认情况下,记住我 Cookie 包含一个哈希值,该哈希值用于验证 Cookie。此哈希值基于配置的签名属性计算得出。

这些属性始终包含在哈希值中

最重要的是,您可以使用 signature_properties 设置配置自定义属性(默认为 password)。这些属性使用 PropertyAccess 组件从用户对象中获取(例如,当使用 updatedAt 时,使用 getUpdatedAt() 或公共 $updatedAt 属性)。

1
2
3
4
5
6
7
8
9
10
# config/packages/security.yaml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                # ...
                signature_properties: ['password', 'updatedAt']

在此示例中,如果此用户的 updatedAt、密码或用户标识符发生更改,则记住我 Cookie 将不再被视为有效。

提示

签名属性允许一些高级用法,而无需为所有记住我令牌设置存储。例如,您可以向您的用户和签名属性添加 forceReloginAt 字段。这样,您可以通过更改此时间戳来使来自用户的所有记住我令牌失效。

在数据库中存储记住我令牌

由于记住我令牌通常是长期存在的,您可能更喜欢将它们保存在数据库中以便完全控制它们。Symfony 提供了对持久记住我令牌的支持。

此实现使用记住我令牌提供程序来存储和检索数据库中的令牌。DoctrineBridge 提供了一个使用 Doctrine 的令牌提供程序。

您可以使用 doctrine 设置启用 doctrine 令牌提供程序

1
2
3
4
5
6
7
8
9
10
11
# config/packages/security.yaml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                # ...
                token_provider:
                    doctrine: true

这也指示 Doctrine 为记住我令牌创建一个表。如果您使用 DoctrineMigrationsBundle,您可以为此创建一个新的迁移

1
2
3
4
$ php bin/console doctrine:migrations:diff

# and optionally run the migrations locally
$ php bin/console doctrine:migrations:migrate

否则,您可以使用 doctrine:schema:update 命令

1
2
3
4
5
# get the required SQL code
$ php bin/console doctrine:schema:update --dump-sql

# run the SQL in your DB client, or let the command run it for you
$ php bin/console doctrine:schema:update --force

实现自定义令牌提供程序

您还可以通过创建一个实现 TokenProviderInterface 的类来创建自定义令牌提供程序。

然后,将您的自定义令牌提供程序的 Service ID 配置为 service

1
2
3
4
5
6
7
8
9
10
11
# config/packages/security.yaml
security:
    # ...

    firewalls:
        main:
            # ...
            remember_me:
                # ...
                token_provider:
                    service: App\Security\RememberMe\CustomTokenProvider

强制用户在访问特定资源前重新验证身份

当用户返回您的站点时,他们会根据记住我 Cookie 中存储的信息自动进行身份验证。这允许用户访问受保护的资源,就好像用户在访问站点时实际已通过身份验证一样。

但是,在某些情况下,您可能希望强制用户在访问某些资源之前实际重新验证身份。例如,您可能不允许“记住我”用户更改其密码。您可以通过利用一些特殊的“属性”来做到这一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// src/Controller/AccountController.php
// ...

public function accountInfo(): Response
{
    // allow any authenticated user - we don't care if they just
    // logged in, or are logged in via a remember me cookie
    $this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');

    // ...
}

public function resetPassword(): Response
{
    // require the user to log in during *this* session
    // if they were only logged in via a remember me cookie, they
    // will be redirected to the login page
    $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

    // ...
}

提示

还有一个 IS_REMEMBERED 属性,该属性在用户通过记住我机制进行身份验证时授予访问权限。

remember_me 配置包含许多选项,用于自定义系统创建的 Cookie

name(默认值:REMEMBERME
用于保持用户登录状态的 Cookie 的名称。如果您在同一应用程序的多个防火墙中启用了 remember_me 功能,请确保为每个防火墙的 Cookie 选择不同的名称。否则,您将面临许多与安全相关的问题。
lifetime(默认值:31536000,即 1 年,以秒为单位)
Cookie 过期前的秒数。这定义了用户保持身份验证状态的两次访问之间的最大时间。
path(默认值:/
与此功能关联的 Cookie 使用的路径。默认情况下,Cookie 将应用于整个网站,但您可以限制为特定部分(例如 /forum/admin)。
domain(默认值:null
与此功能关联的 Cookie 使用的域。默认情况下,Cookie 使用从 $_SERVER 获取的当前域。
secure(默认值:false
如果为 true,则与此功能关联的 Cookie 将通过 HTTPS 安全连接发送给用户。
httponly(默认值:true
如果为 true,则与此功能关联的 Cookie 只能通过 HTTP 协议访问。这意味着 Cookie 将无法通过脚本语言(例如 JavaScript)访问。
samesite(默认值:null
如果设置为 strict,则与此功能关联的 Cookie 将不会与跨站点请求一起发送,即使在遵循常规链接时也是如此。
本作品,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本