symfonycasts/verify-email-bundle

为Symfony提供的简单、时尚的电子邮件验证

安装次数: 2,652,544

依赖项: 17

建议者: 0

安全: 0

星标: 399

关注者: 12

分支: 32

开放问题: 12

类型:symfony-bundle

v1.17.0 2024-03-17 02:29 UTC

README

不知道用户是否有有效的电子邮件地址?VerifyEmailBundle可以帮助您!

VerifyEmailBundle生成并验证一个安全的、经过签名的URL,可以将其通过电子邮件发送给用户以确认他们的电子邮件地址。它无需存储,因此您可以通过对现有实体进行少量修改来使用它。此包提供

  • 一个生成要发送给用户的签名URL的生成器。
  • 一个签名URL验证器。
  • 知道这样做不会将用户的电子邮件地址泄露到您的服务器日志中(避免PII问题),您可以放心。

安装

当然使用Composer!

composer require symfonycasts/verify-email-bundle

用法

我们强烈建议使用Symfony MakerBundle的make:registration-form命令来了解如何使用此包。它非常简单!回答几个问题,您将拥有一个带有电子邮件验证的完整功能的注册系统。

bin/console make:registration-form

手动设置

如果您想手动设置,当然可以!但请谨慎操作:电子邮件验证是一个敏感的安全过程。我们将指导您完成重要步骤。使用make:registration-form仍然是简单且简单的方法。

以下示例演示了生成要发送给用户并经过电子邮件确认的签名URL的基本步骤。用户点击电子邮件中的链接后,将验证此URL。

// RegistrationController.php
namespace App\Controller;

use App\Entity\User;
use App\Form\RegistrationFormType;
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\Attribute\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;

class RegistrationController extends AbstractController
{
    public function __construct(private 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('mailer@example.com', 'AcmeMailBot'))
                    ->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_main');
        }

        return $this->render('registration/register.html.twig', [
            'registrationForm' => $form,
        ]);
    }

    #[Route('/verify/email', name: 'app_verify_email')]
    public function verifyUserEmail(Request $request, TranslatorInterface $translator): Response
    {
        $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');

        // validate email confirmation link, sets User::isVerified=true and persists
        try {
            $this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
        } 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类,您也应该将其添加到您的应用程序中:

// src/Security/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()
        );

        $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->validateEmailConfirmationFromRequest($request, $user->getId(), $user->getEmail());

        $user->setIsVerified(true);

        $this->entityManager->persist($user);
        $this->entityManager->flush();
    }
}

匿名验证

也可以允许用户在不进行身份验证的情况下验证他们的电子邮件地址。这种情况的一个用例是,如果用户在笔记本电脑上注册,但在手机上点击验证链接。通常,用户在验证电子邮件之前需要登录。

我们可以通过在签名URL中传递用户标识符作为查询参数来解决这个问题。以下示例演示了如何基于之前的示例完成此操作

// src/Security/EmailVerifier.php

class EmailVerifier
{
    // ...

    public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void
    {
        $user = new User();

        // handle the user registration form and persist the new user...
    
        $signatureComponents = $this->verifyEmailHelper->generateSignature(
                $verifyEmailRouteName,
                $user->getId(),
                $user->getEmail(),
+               ['id' => $user->getId()] // add the user's id as an extra query param
            );
    }
}

用户收到电子邮件并点击链接后,注册控制器将在以下方法中验证签名URL:

// RegistrationController.php

+use App\Repository\UserRepository;

class RegistrationController extends AbstractController
{
-   public function verifyUserEmail(Request $request): Response
+   public function verifyUserEmail(Request $request, UserRepository $userRepository): Response
    {
-       $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
-       $user = $this->getUser();

+       $id = $request->query->get('id'); // retrieve the user id from the url
+
+       // Verify the user id exists and is not null
+       if (null === $id) {
+           return $this->redirectToRoute('app_home');
+       }
+
+       $user = $userRepository->find($id);
+
+       // Ensure the user exists in persistence
+       if (null === $user) {
+           return $this->redirectToRoute('app_home');
+       }

        try {
            $this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, $user->getId(), $user->getEmail());
        } catch (VerifyEmailExceptionInterface $e) {
        // ...
    }
}

配置

您可以通过创建一个config/packages/verify_email.yaml配置文件来更改包的默认配置参数

symfonycasts_verify_email:
    lifetime: 3600

lifetime

可选 - 默认为3600

这是签名URL在创建后多少秒内有效的秒数。

保留查询参数

如果您在verifyEmailHelper::generateSignature()的第五个参数中添加任何额外的查询参数,例如我们上面为id所做的那样,请注意您不能使用以下查询参数,因为它们将被此包覆盖

  • token
  • expires
  • signature

支持

请随时为我们包的疑问、问题或建议创建一个问题。有关Symfony的MakerBundle(特别是make:registration-form)的问题应在Symfony Maker存储库中解决。

安全问题

关于安全相关漏洞,我们要求您通过电子邮件联系 ryan@symfonycasts.com 而不是创建一个问题。

这将给我们机会在不发布修复方案之前解决该问题,避免在修复方案发布前泄露漏洞。