我有一个 Symfony 7 项目,我正在其中为登录路由 (/api/login) 实现自定义身份验证逻辑。我的控制器中有一个登录方法,用于处理用户登录并根据提供的名称生成令牌。此外,我创建了一个自定义 AuthenticationListener 类,该类实现 AuthenticatorInterface 来处理其他路由的身份验证。
相关代码总结如下:
<?php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
class AuthenticationListener implements AuthenticatorInterface
{
public function supports(Request $request): ?bool
{
$route = $request->attributes->get('_route');
return str_starts_with($route, 'api_');
}
#[AsEventListener(event: 'kernel.request')]
public function authenticate(RequestEvent|Request $event): Passport
{
$request = $event->getRequest();
// Check if the request is for the login route
if ($request->attributes->get('_route') === 'login') {
return new Passport();
}
if (!$request->headers->has('Authorization')) {
throw new UnauthorizedHttpException('Unauthorized', 'Authorization header is missing');
}
$key = $request->headers->get('Authorization');
if (!$key) {
throw new UnauthorizedHttpException('Unauthorized', 'Authorization header is missing');
}
$decryptedName = $this->decrypt($key);
if (!$decryptedName) {
throw new UnauthorizedHttpException('Unauthorized', 'Invalid or expired token');
}
// Create a UserBadge for other routes
$userBadge = new UserBadge($decryptedName, function ($username) {
return null;
});
return new Passport($userBadge);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return new Response('Authenticated Successfully', Response::HTTP_OK);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
return new Response('Unauthorized', Response::HTTP_UNAUTHORIZED);
}
private function decrypt($string): string
{
// Decrypt the authorization token using your decryption method
$secret_key = $_ENV['APP_SECRET']; // Ensure you have APP_KEY set in your environment variables
$key = openssl_digest($secret_key, 'SHA256', TRUE);
$c = base64_decode($string);
$ivlen = openssl_cipher_iv_length($cipher = "AES-128-CBC");
$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len = 32);
$ciphertext_raw = substr($c, $ivlen + $sha2len);
$original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, OPENSSL_RAW_DATA, $iv);
// dd($original_plaintext);
$calcmac = hash_hmac('sha256', $ciphertext_raw, $key, true);
if (hash_equals($hmac, $calcmac)) {
return $original_plaintext;
}
return ''; // Return an empty string if decryption fails
}
public function createToken(Passport $passport, string $firewallName): TokenInterface
{
throw new \LogicException('This method should not be called for stateless authentication.');
}
}
class UserController extends AbstractController
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/api/login', name: 'login', methods: ['POST'])]
public function login(Request $request): JsonResponse
{
// Retrieve the request data
$requestData = json_decode($request->getContent(), true);
// Ensure the required fields are provided
if (!isset($requestData['name'])) {
return new JsonResponse(['error' => 'Name field is required'], Response::HTTP_BAD_REQUEST);
}
$name = $requestData['name'];
// Encrypt the name
$encryptedName = $this->encrypt($name);
// Generate a token based on the encrypted name
$token = $this->generateToken($encryptedName);
// Return the token to the client
return new JsonResponse(['token' => $token]);
}
private function encrypt(string $name): string
{
$secretKey = $_ENV['APP_SECRET'];
$key = openssl_digest($secretKey, 'SHA256', TRUE);
$ivlen = openssl_cipher_iv_length($cipher = "AES-128-CBC");
$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertextRaw = openssl_encrypt($name, $cipher, $key, OPENSSL_RAW_DATA, $iv);
$hmac = hash_hmac('sha256', $ciphertextRaw, $key, true);
$output = base64_encode($iv . $hmac . $ciphertextRaw);
return $output;
}
private function generateToken(string $encryptedName): string
{
// Generate a token based on the encrypted name
return hash('sha256', $encryptedName);
}
尽管我付出了努力,但我在绕过登录路由的身份验证并确保其他路由的正确身份验证方面遇到了挑战。我尝试了不同的配置,但我不断收到与丢失或无效授权标头相关的错误。
有人可以提供有关如何正确实现登录路由的自定义身份验证逻辑的指导,同时确保其他路由的身份验证保持不变吗?任何见解、代码示例或配置建议将不胜感激。谢谢!
由于您的登录不需要授权,因此您的访问控制应该允许公共访问:
# config/packages/security.yaml
security:
# ...
access_control:
- { path: '^/api/login', roles: PUBLIC_ACCESS }
了解更多关于允许不安全访问