Symfony Remember Me 功能与 Cookie 详解
基本配置
security.yaml 配置
# config/packages/security.yaml
security:
firewalls:
main:
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 7天,单位秒
path: /
domain: ~ # 默认为当前域
secure: false # 是否仅HTTPS
httponly: true
samesite: lax
always_remember_me: false
remember_me_parameter: _remember_me # 表单中的字段名
登录表单实现
{# templates/security/login.html.twig #}
<form method="post">
<input type="email" name="_username" value="{{ last_username }}">
<input type="password" name="_password">
{# Remember me checkbox #}
<label>
<input type="checkbox" name="_remember_me" checked>
记住我
</label>
<button type="submit">登录</button>
</form>
自定义 Remember Me 服务
// src/Security/RememberMeService.php
namespace App\Security;
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
use Symfony\Component\Security\Core\Authentication\RememberMe\TokenProviderInterface;
class RememberMeService implements TokenProviderInterface
{
private $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
public function loadTokenBySeries(string $series): PersistentToken
{
// 从数据库加载token
$stmt = $this->connection->prepare(
'SELECT * FROM remember_me_tokens WHERE series = ?'
);
$stmt->execute([$series]);
if (!$data = $stmt->fetch()) {
throw new \Symfony\Component\Security\Core\Exception\TokenNotFoundException();
}
return new PersistentToken(
$data['class'],
$data['username'],
$data['series'],
$data['value'],
new \DateTime($data['last_used'])
);
}
public function createToken(\Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface $token): void
{
// 创建新的remember me token
$stmt = $this->connection->prepare(
'INSERT INTO remember_me_tokens (class, username, series, value, last_used) VALUES (?, ?, ?, ?, ?)'
);
$stmt->execute([
$token->getClass(),
$token->getUsername(),
$token->getSeries(),
$token->getValue(),
$token->getLastUsed()->format('Y-m-d H:i:s')
]);
}
public function deleteTokenBySeries(string $series): void
{
// 删除token
$stmt = $this->connection->prepare(
'DELETE FROM remember_me_tokens WHERE series = ?'
);
$stmt->execute([$series]);
}
public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed): void
{
// 更新token
$stmt = $this->connection->prepare(
'UPDATE remember_me_tokens SET value = ?, last_used = ? WHERE series = ?'
);
$stmt->execute([$tokenValue, $lastUsed->format('Y-m-d H:i:s'), $series]);
}
}
Cookie 管理
自定义 Cookie 配置
// src/EventListener/RememberMeCookieListener.php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class RememberMeCookieListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
LoginSuccessEvent::class => 'onLoginSuccess',
LogoutEvent::class => 'onLogout',
];
}
public function onLoginSuccess(LoginSuccessEvent $event): void
{
// 登录成功后可以添加额外的cookie处理
$request = $event->getRequest();
$response = $event->getResponse();
// 设置自定义cookie
$this->setCustomCookie($response);
}
public function onLogout(LogoutEvent $event): void
{
// 退出时清除所有相关cookie
$response = $event->getResponse();
$this->clearRememberMeCookie($response);
}
private function setCustomCookie($response): void
{
$cookie = new \Symfony\Component\HttpFoundation\Cookie(
'custom_remember_me',
'some_value',
time() + 3600 * 24 * 30, // 30天
'/',
null,
false, // secure
true, // httponly
false,
'lax'
);
$response->headers->setCookie($cookie);
}
private function clearRememberMeCookie($response): void
{
$response->headers->clearCookie('REMEMBERME');
}
}
数据库表结构
-- 持久化token 表
CREATE TABLE remember_me_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
class VARCHAR(255) NOT NULL,
username VARCHAR(255) NOT NULL,
series VARCHAR(88) NOT NULL UNIQUE,
value VARCHAR(88) NOT NULL,
last_used DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_username (username),
INDEX idx_series (series)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
安全检查与验证
// src/Security/RememberMeChecker.php
namespace App\Security;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
class RememberMeChecker extends AbstractRememberMeServices
{
protected function processAutoLoginCookie(array $cookieParts, Request $request): ?UserInterface
{
// 验证remember me cookie
if (count($cookieParts) !== 3) {
throw new AuthenticationException('Invalid cookie format');
}
[$series, $tokenValue, $username] = $cookieParts;
// 验证用户是否存在
$user = $this->userProvider->loadUserByIdentifier($username);
if (!$user) {
throw new AuthenticationException('User not found');
}
// 验证token
if (!$this->validateToken($series, $tokenValue)) {
throw new AuthenticationException('Invalid token');
}
return $user;
}
private function validateToken(string $series, string $tokenValue): bool
{
// 实现token验证逻辑
return true; // 简化示例
}
public function loginSuccess(Request $request, Response $response, TokenInterface $token): void
{
// 登录成功后的处理
}
public function logout(Request $request, Response $response, TokenInterface $token): void
{
// 退出时的处理
}
}
最佳实践
-
安全配置:

# 安全建议配置 remember_me: secret: '%kernel.secret%' name: REMEMBERME lifetime: 2592000 # 30天 path: / domain: ~ secure: true # 生产环境建议true httponly: true samesite: strict persistent: true # 使用数据库存储
-
清理过期token:
// src/Command/CleanRememberMeTokensCommand.php namespace App\Command;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface;
class CleanRememberMeTokensCommand extends Command { protected static $defaultName = 'app:clean-remember-me-tokens';
protected function configure(): void
{
$this->setDescription('Clean expired remember me tokens');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
// 清理过期token
$this->entityManager
->createQuery('DELETE FROM App\Entity\RememberMeToken t WHERE t.lastUsed < :date')
->setParameter('date', new \DateTime('-30 days'))
->execute();
$output->writeln('Expired tokens cleaned');
return Command::SUCCESS;
}
3. **定期清理cron job**:
```bash
# crontab
0 3 * * * php /path/to/project/bin/console app:clean-remember-me-tokens
这样配置的Remember Me功能可以安全地让用户保持登录状态,同时提供了完善的cookie管理和安全验证机制。