如何使用Symfony 4生成临时API密钥?移动端API对接认证需求
实现Symfony API的临时登录后API密钥生成方案
我之前做过类似的Symfony API认证场景,刚好能解决你提到的问题——用户登录后生成临时API密钥用于后续请求。下面是具体的实现步骤,结合Symfony的Security组件来完成:
1. 准备API密钥存储实体
首先需要在数据库中存储用户的临时API密钥,新建一个ApiKey实体来管理密钥的关联用户、过期时间等信息:
// src/Entity/ApiKey.php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use App\Entity\User; #[ORM\Entity] class ApiKey { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\Column(type: 'string', length: 255, unique: true)] private string $key; #[ORM\Column(type: 'datetime_immutable')] private \DateTimeImmutable $expiresAt; #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(nullable: false)] private User $user; // 构造函数自动生成密钥和过期时间 public function __construct(User $user) { $this->user = $user; // 生成64位随机字符串作为密钥,安全性足够 $this->key = bin2hex(random_bytes(32)); // 设置1小时过期,可根据业务需求调整时长 $this->expiresAt = new \DateTimeImmutable('+1 hour'); } // 此处省略getter和setter方法,按需添加即可 }
2. 实现登录接口生成密钥
创建一个登录控制器,接收移动端传来的账号密码,验证通过后生成并返回临时API密钥(建议同时清理用户旧密钥,避免多密钥同时有效):
// src/Controller/ApiLoginController.php namespace App\Controller; use App\Entity\ApiKey; use App\Entity\User; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class ApiLoginController extends AbstractController { public function __invoke( Request $request, UserPasswordHasherInterface $passwordHasher, EntityManagerInterface $em ): JsonResponse { $data = json_decode($request->getContent(), true); // 根据邮箱查找用户 $user = $em->getRepository(User::class)->findOneBy(['email' => $data['email']]); // 验证用户存在且密码正确 if (!$user || !$passwordHasher->isPasswordValid($user, $data['password'])) { return new JsonResponse(['message' => 'Invalid credentials'], 401); } // 删除用户旧的API密钥,确保每次登录只有一个有效密钥 $oldKeys = $em->getRepository(ApiKey::class)->findBy(['user' => $user]); foreach ($oldKeys as $key) { $em->remove($key); } // 生成新密钥并持久化到数据库 $apiKey = new ApiKey($user); $em->persist($apiKey); $em->flush(); // 返回密钥和过期时间给移动端 return new JsonResponse([ 'api_key' => $apiKey->getKey(), 'expires_at' => $apiKey->getExpiresAt()->format(\DateTimeInterface::RFC3339) ]); } }
3. 配置API密钥认证器
按照官方文档的认证思路,创建一个自定义Authenticator,用于验证请求头中的API密钥是否有效、未过期:
// src/Security/ApiKeyAuthenticator.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\CustomUserMessageAuthenticationException; 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 { return $request->headers->has('X-AUTH-TOKEN'); } // 执行认证逻辑 public function authenticate(Request $request): Passport { $apiKey = $request->headers->get('X-AUTH-TOKEN'); if (null === $apiKey) { throw new CustomUserMessageAuthenticationException('No API key provided'); } return new SelfValidatingPassport( new UserBadge($apiKey, function ($apiKey) { $keyEntity = $this->em->getRepository(ApiKey::class)->findOneBy(['key' => $apiKey]); // 验证密钥存在且未过期 if (!$keyEntity || $keyEntity->getExpiresAt() < new \DateTimeImmutable()) { throw new CustomUserMessageAuthenticationException('Invalid or expired API key'); } return $keyEntity->getUser(); }) ); } // 认证成功后的处理(这里直接放行,让控制器继续执行) public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response { return null; } // 认证失败后的返回 public function onAuthenticationFailure(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $exception): ?Response { return new JsonResponse([ 'message' => strtr($exception->getMessageKey(), $exception->getMessageData()) ], Response::HTTP_UNAUTHORIZED); } }
4. 配置Security.yaml
最后在config/packages/security.yaml中配置认证规则,指定API路由使用上述认证器:
security: providers: app_user_provider: entity: class: App\Entity\User property: email firewalls: api: pattern: ^/api/ stateless: true custom_authenticator: App\Security\ApiKeyAuthenticator access_control: - { path: ^/api/login, roles: PUBLIC_ACCESS } - { path: ^/api/, roles: ROLE_USER }
这样一来,用户在移动端登录后会拿到一个临时API密钥,后续请求只要在请求头带上X-AUTH-TOKEN: {你的密钥},Symfony就会自动验证密钥的有效性和过期时间,完成认证流程。
内容的提问来源于stack exchange,提问作者xil3




