You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 20:55:35