You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用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

火山引擎 最新活动