Symfony应用中邮箱验证链接过期后获取新链接的实现
实现Symfony邮箱验证链接过期后的重发功能
我已经在Symfony应用中完成安全流程搭建,所有功能均正常运行,但不清楚当邮箱验证链接过期时,如何实现「请求新链接」的功能。以下是我的相关代码,寻求实现方法。
Security配置
security: # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: # used to reload user from session & other features (e.g. switch_user) app_user_provider: entity: class: App\Entity\User property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true provider: app_user_provider login_throttling: max_attempts: 3 logout: path: app_logout form_login: login_path: app_login check_path: app_login default_target_path: app_member remember_me: secret: '%kernel.secret%' # required lifetime: 604800 # 1 week in seconds access_control: # - { path: ^/admin, roles: ROLE_ADMIN } - { path: ^/member, roles: ROLE_USER }
RegistrationController代码
namespace App\Controller; use App\Entity\User; use App\Form\RegistrationFormType; use App\Repository\UserRepository; use App\Security\EmailVerifier; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Mime\Address; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\Translation\TranslatorInterface; use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface; class RegistrationController extends AbstractController { private EmailVerifier $emailVerifier; public function __construct(EmailVerifier $emailVerifier) { $this->emailVerifier = $emailVerifier; } #[Route('/register', name: 'app_register')] public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response { $user = new User(); $form = $this->createForm(RegistrationFormType::class, $user); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // encode the plain password $user->setPassword( $userPasswordHasher->hashPassword( $user, $form->get('plainPassword')->getData() ) ); $entityManager->persist($user); $entityManager->flush(); // generate a signed url and email it to the user $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, (new TemplatedEmail()) ->from(new Address('info@anteriority.ch', 'Account Confirmation')) ->to($user->getEmail()) ->subject('Please Confirm your Email') ->htmlTemplate('registration/confirmation_email.html.twig') ); // do anything else you need here, like send an email return $this->redirectToRoute('app_login'); } return $this->render('registration/register.html.twig', [ 'registrationForm' => $form->createView(), ]); } #[Route('/verify/email', name: 'app_verify_email')] public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response { $id = $request->query->get('id'); if (null === $id) { return $this->redirectToRoute('app_register'); } $user = $userRepository->find($id); if (null === $user) { return $this->redirectToRoute('app_register'); } // validate email confirmation link, sets User::isVerified=true and persists try { $this->emailVerifier->handleEmailConfirmation($request, $user); } catch (VerifyEmailExceptionInterface $exception) { $this->addFlash('verify_email_error', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle')); return $this->redirectToRoute('app_register'); } // @TODO Change the redirect on success and handle or remove the flash message in your templates $this->addFlash('success', 'Your email address has been verified.'); return $this->redirectToRoute('app_register'); } }
EmailVerifier代码
<?php namespace App\Security; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Security\Core\User\UserInterface; use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface; use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface; class EmailVerifier { public function __construct( private VerifyEmailHelperInterface $verifyEmailHelper, private MailerInterface $mailer, private EntityManagerInterface $entityManager ) { } public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void { $signatureComponents = $this->verifyEmailHelper->generateSignature( $verifyEmailRouteName, $user->getId(), $user->getEmail(), ['id' => $user->getId()] ); $context = $email->getContext(); $context['signedUrl'] = $signatureComponents->getSignedUrl(); $context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey(); $context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData(); $email->context($context); $this->mailer->send($email); } /** * @throws VerifyEmailExceptionInterface */ public function handleEmailConfirmation(Request $request, UserInterface $user): void { $this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getEmail()); $roles = []; $roles[] = "ROLE_MEMBER"; $user->setRoles($roles); $user->setIsVerified(true); $this->entityManager->persist($user); $this->entityManager->flush(); } }
实现步骤
1. 添加重发验证邮件的路由与方法
在RegistrationController中新增处理重发请求的代码:
// 记得引入必要的表单类 use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Email; #[Route('/resend-verification', name: 'app_resend_verification')] public function resendVerification(Request $request, UserRepository $userRepository, TranslatorInterface $translator): Response { // 创建邮箱输入表单 $form = $this->createFormBuilder() ->add('email', EmailType::class, [ 'label' => '输入你的邮箱', 'constraints' => [ new NotBlank(['message' => '请输入邮箱地址']), new Email(['message' => '请输入有效的邮箱地址']), ] ]) ->add('submit', SubmitType::class, ['label' => '重新发送验证链接']) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $email = $form->get('email')->getData(); $user = $userRepository->findOneBy(['email' => $email]); // 仅向未验证的用户发送新链接 if (!$user || $user->isVerified()) { $this->addFlash('error', $translator->trans('用户不存在或已完成邮箱验证')); return $this->redirectToRoute('app_resend_verification'); } // 调用已有的发送方法生成新的签名链接 $this->emailVerifier->sendEmailConfirmation('app_verify_email', $user, (new TemplatedEmail()) ->from(new Address('info@anteriority.ch', 'Account Confirmation')) ->to($user->getEmail()) ->subject('重新发送:请验证你的邮箱') ->htmlTemplate('registration/confirmation_email.html.twig') ); $this->addFlash('success', '新的验证链接已发送至你的邮箱'); return $this->redirectToRoute('app_login'); } return $this->render('registration/resend_verification.html.twig', [ 'resendForm' => $form->createView(), ]); }
2. 创建重发页面模板
在templates/registration/目录下创建resend_verification.html.twig:
{% extends 'base.html.twig' %} {% block title %}重新发送验证链接{% endblock %} {% block body %} <h1>重新发送邮箱验证链接</h1> {% for flash_error in app.flashes('error') %} <div class="alert alert-danger">{{ flash_error }}</div> {% endfor %} {% for flash_success in app.flashes('success') %} <div class="alert alert-success">{{ flash_success }}</div> {% endfor %} {{ form_start(resendForm) }} {{ form_widget(resendForm) }} <button type="submit" class="btn btn-primary">发送链接</button> {{ form_end(resendForm) }} {% endblock %}
3. 修改验证失败后的跳转逻辑
在verifyUserEmail方法的异常捕获块中,重定向到重发页面:
catch (VerifyEmailExceptionInterface $exception) { $this->addFlash('verify_email_error', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle')); // 替换原跳转,指向重发页面 return $this->redirectToRoute('app_resend_verification'); }
4. 添加入口链接
在登录页面或注册成功页面添加重发入口,比如在login.html.twig中:
<p>还未验证邮箱?<a href="{{ path('app_resend_verification') }}">点击获取新的验证链接</a></p>
额外配置(可选)
若需要调整验证链接的过期时间,可修改config/packages/symfony_casts_verify_email.yaml:
symfony_casts_verify_email: lifetime: 3600 # 单位:秒,默认1小时,可根据需求调整
内容的提问来源于stack exchange,提问作者thicre
相关产品推荐
相关产品推荐

