📌  相关文章
📜  生成令牌 symfony 身份验证 api 平台 - Shell-Bash (1)

📅  最后修改于: 2023-12-03 15:27:08.211000             🧑  作者: Mango

生成令牌 Symfony 身份验证 API 平台

Symfony 是一个广泛使用的 PHP MVC 框架,它提供了一个身份验证组件来实现用户身份验证和授权。在本文中,我们将探讨如何使用 Symfony 身份验证组件创建一个 API 平台,以生成令牌来验证用户身份。

步骤

首先,我们需要安装 Symfony。在终端中运行以下命令:

composer create-project symfony/website-skeleton my_project_name

接下来,我们需要安装身份验证组件。在终端中运行以下命令:

composer require symfony/security-bundle

现在,我们需要配置身份验证组件。打开 config/packages/security.yaml 文件,并添加以下内容:

security:
    encoders:
        App\Entity\User:
            algorithm: bcrypt
    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        api:
            pattern: ^/api
            stateless: true
            anonymous: false
            provider: app_user_provider
            guard:
                authenticators:
                    - App\Security\TokenAuthenticator
    access_control:
        - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api,       roles: IS_AUTHENTICATED_FULLY }

这个配置文件中定义了一个名为 api 的防火墙,用于保护 API 路径。我们还定义了一个自定义的身份验证器 TokenAuthenticator,它将在接下来的步骤中实现。

接下来,我们需要创建一个用户实体类。在终端中运行以下命令:

bin/console make:user

这个命令将创建一个用户实体类。我们需要在这个类中实现 UserInterface 接口,并添加一个用于存储用户访问令牌的属性。

// src/Entity/User.php
namespace App\Entity;

use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=254, unique=true)
     */
    private $email;

    /**
     * @ORM\Column(type="json")
     */
    private $roles = [];

    /**
     * @ORM\Column(type="string")
     */
    private $password;

    /**
     * @ORM\Column(type="string", length=32)
     */
    private $apiToken;

    // ...

    public function getApiToken(): ?string
    {
        return $this->apiToken;
    }

    public function setApiToken(string $apiToken): self
    {
        $this->apiToken = $apiToken;

        return $this;
    }
}

现在我们需要实现 TokenAuthenticator。在终端中运行以下命令:

bin/console make:auth

这个命令将创建一个身份验证器类,我们需要在其中实现令牌生成和验证逻辑。

// src/Security/TokenAuthenticator.php
namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\Authenticator\AbstractGuardAuthenticator;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;

class TokenAuthenticator extends AbstractGuardAuthenticator
{
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function supports(Request $request)
    {
        return $request->headers->has('Authorization') && preg_match('/^Bearer (.*)$/', $request->headers->get('Authorization'), $matches);
    }

    public function getCredentials(Request $request)
    {
        $authorizationHeader = $request->headers->get('Authorization');
        preg_match('/^Bearer (.*)$/', $authorizationHeader, $matches);
        $token = $matches[1];

        return ['token' => $token];
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = $credentials['token'];

        if (null === $token) {
            return;
        }

        return $this->entityManager->getRepository(User::class)->findOneBy(['apiToken' => $token]);
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return true;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        throw new CustomUserMessageAuthenticationException('Invalid or expired token');
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {

    }

    public function start(Request $request, AuthenticationException $authException = null)
    {
        throw new CustomUserMessageAuthenticationException('Authentication required');
    }

    public function supportsRememberMe()
    {
        return false;
    }
}

以上代码中,我们通过 getCredentials 方法获取请求中的访问令牌,并通过 getUser 方法获取与此令牌关联的用户实体。如果找不到用户实体,则身份验证失败。通过 checkCredentials 方法实现令牌验证,并在 onAuthenticationFailure 方法中处理身份验证失败的情况。

最后,我们需要将令牌存储在用户实体中,并在 API 登录成功时生成令牌。我们可以通过创建一个登录控制器来实现这个逻辑。

// src/Controller/SecurityController.php
namespace App\Controller;

use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class SecurityController extends AbstractController
{
    /**
     * @Route("/api/login", name="login", methods={"POST"})
     */
    public function login(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
    {
        $email    = $request->request->get('email');
        $password = $request->request->get('password');

        $user = $this->getDoctrine()->getRepository(User::class)->findOneBy(['email' => $email]);

        if (!$user || !$passwordEncoder->isPasswordValid($user, $password)) {
            throw $this->createAccessDeniedException('Invalid email or password');
        }

        $user->setApiToken(md5(random_bytes(10)));

        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($user);
        $entityManager->flush();

        return new JsonResponse(['token' => $user->getApiToken()]);
    }
}

以上代码中,我们使用 findOneBy 方法从数据库中获取与请求中提供的电子邮件地址对应的用户实体,使用 $passwordEncoder 实例验证提供的密码,并通过 md5(random_bytes(10)) 生成随机的访问令牌。

结论

通过以上步骤,我们已经成功使用 Symfony 身份验证组件实现了一个简单的 API 平台,并使用自定义身份验证器实现了令牌生成和验证逻辑。这个 API 平台可以与任何前端应用程序集成,以提供基于令牌的身份验证和授权体验。