如何在Symfony 6中实现API Key身份验证
Symfony 简单API Key身份验证实现方案
一、创建API Key实体与数据库表
先通过Symfony命令生成实体,用于存储API密钥信息:
php bin/console make:entity ApiKey
添加以下字段:
apiKey: 字符串类型,设置唯一索引,建议长度64位(存储随机生成的密钥)user: 关联到你的User实体(可选,用于绑定密钥所属用户)isActive: 布尔类型,默认true(控制密钥是否可用)expiresAt: 日期时间类型(可选,设置密钥过期时间)
生成迁移并执行:
php bin/console make:migration php bin/console doctrine:migrations:migrate
二、编写自定义API Key验证器
创建src/Security/ApiKeyAuthenticator.php,实现核心验证逻辑:
<?php namespace App\Security; use App\Entity\ApiKey; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 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\Http\Authenticator\AbstractAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; class ApiKeyAuthenticator extends AbstractAuthenticator { public function __construct(private EntityManagerInterface $em) { } public function supports(Request $request): ?bool { // 仅对API路由生效,可根据你的路由前缀调整 return str_starts_with($request->getPathInfo(), '/api/'); } public function authenticate(Request $request): Passport { // 从请求头获取API Key $apiKey = $request->headers->get('X-API-KEY'); if (!$apiKey) { throw new CustomUserMessageAuthenticationException('API Key未提供'); } return new SelfValidatingPassport( new UserBadge($apiKey, function ($apiKey) { $apiKeyEntity = $this->em->getRepository(ApiKey::class)->findOneBy(['apiKey' => $apiKey]); if (!$apiKeyEntity || !$apiKeyEntity->isActive()) { throw new CustomUserMessageAuthenticationException('无效或已禁用的API Key'); } // 检查过期时间(如果配置了) if ($apiKeyEntity->getExpiresAt() && new \DateTime() > $apiKeyEntity->getExpiresAt()) { throw new CustomUserMessageAuthenticationException('API Key已过期'); } // 返回关联用户,无需关联可返回匿名用户或自定义用户对象 return $apiKeyEntity->getUser(); }) ); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { // 验证成功,继续执行请求 return null; } public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { $data = [ 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()) ]; return new JsonResponse($data, Response::HTTP_UNAUTHORIZED); } }
三、配置安全文件
修改config/packages/security.yaml,添加API防火墙规则:
security: enable_authenticator_manager: true providers: app_user_provider: entity: class: App\Entity\User property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false api: pattern: ^/api/ stateless: true custom_authenticators: - App\Security\ApiKeyAuthenticator main: lazy: true provider: app_user_provider # 你的主防火墙其他配置... access_control: - { path: ^/api/, roles: ROLE_USER } # 根据权限需求调整,比如ROLE_API_USER
四、生成API Key(可选)
创建命令批量生成有效密钥:
php bin/console make:command GenerateApiKey
编写命令逻辑:
<?php namespace App\Command; use App\Entity\ApiKey; use App\Entity\User; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; class GenerateApiKeyCommand extends Command { protected static $defaultName = 'app:generate-api-key'; protected static $defaultDescription = '生成新的API Key'; public function __construct(private EntityManagerInterface $em) { parent::__construct(); } protected function configure(): void { $this ->addArgument('user-email', InputArgument::REQUIRED, '绑定的用户邮箱') ->addArgument('expires-at', InputArgument::OPTIONAL, '过期时间(格式:Y-m-d H:i:s)'); } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $userEmail = $input->getArgument('user-email'); $expiresAt = $input->getArgument('expires-at'); $user = $this->em->getRepository(User::class)->findOneBy(['email' => $userEmail]); if (!$user) { $io->error('用户不存在'); return Command::FAILURE; } $apiKey = bin2hex(random_bytes(32)); // 生成64位随机字符串 $apiKeyEntity = new ApiKey(); $apiKeyEntity->setApiKey($apiKey); $apiKeyEntity->setUser($user); $apiKeyEntity->setIsActive(true); if ($expiresAt) { $apiKeyEntity->setExpiresAt(new \DateTime($expiresAt)); } $this->em->persist($apiKeyEntity); $this->em->flush(); $io->success(sprintf('API Key生成成功:%s', $apiKey)); return Command::SUCCESS; } }
执行命令生成密钥:
php bin/console app:generate-api-key user@example.com 2025-12-31 23:59:59
五、验证测试
调用API时在请求头添加X-API-KEY: 你的密钥,若密钥无效、过期或未提供,会返回401错误;验证通过则正常返回API数据。
内容的提问来源于stack exchange,提问作者DanoNNN
相关产品推荐
相关产品推荐

