📅  最后修改于: 2023-12-03 15:27:08.211000             🧑  作者: Mango
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 平台可以与任何前端应用程序集成,以提供基于令牌的身份验证和授权体验。