繁体   English   中英

Firebase 认证器 JWT 与 Symfony ZDB974238714CA8DE634A7CE1D08ZF 平台1

[英]Firebase Authenticator JWT with Symfony API Platform

我有一个 Flutter 客户端,它使用 firebase 来创建用户帐户。 用户可以发布到达 web 管理面板的票证,该管理面板由 Symfony 6 和 API 平台构建。

所以我需要2个身份验证器:

  • 1 个原始 Symfony 身份验证器,供管理员使用表单连接和管理工单。
  • 1 验证器 JWT 将检查 Firebase 凭据,返回 JWT,然后允许发布。 所以我保护了我的 API 路线。

我坚持最后一点。 我正在为 Symfony 使用Firebase 捆绑包 SDK 我正在恢复我的 Firebase 用户。 我写了一个FirebaseUserProvider和一个FirebaseAuthenticator

当然,FirebaseUser 实体与 Doctrine ORM 没有任何联系。

我想我不是很远,但我被困住了。 401: Invalid Credentials ” 在我通过 Postman 提交的所有登录信息中。 如果没有我的 SQL 数据库和 Doctrine 的干预,我可以实现这一点吗? 我希望我不必“将 firebase 用户复制到我的 sql 数据库”。

这是我的重要文件。

security:
    enable_authenticator_manager: true
    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
    providers:
        firebase_user_provider:
            id: App\Security\FirebaseUserProvider
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
        jwt:
            lexik_jwt: ~
    firewalls:
        dev:
            pattern: (_(profiler|wdt)|css|images|js)/
            security: false

        # APILogin
        login:
            pattern: ^/api/login$
            stateless: true
            custom_authenticators:
                - App\Security\FirebaseAuthenticator
            provider: firebase_user_provider
            json_login:
                check_path: /api/login
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
        api:
            pattern: ^/api
            stateless: true
            provider: jwt
            jwt: ~

        # AppFormLogin
        main:
            lazy: true
            stateless: true
            provider: app_user_provider
            custom_authenticator: App\Security\LoginFormAuthenticator
            logout:
                path: app_logout

            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#the-firewall

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/api/login, roles: PUBLIC_ACCESS }
#        - { path: ^/api,       roles: IS_AUTHENTICATED_FULLY }
class FirebaseAuthenticator extends AbstractAuthenticator
{
    private FirebaseUserProvider $firebaseUserProvider;

    public function __construct(FirebaseUserProvider $firebaseUserProvider)
    {
        $this->firebaseUserProvider = $firebaseUserProvider;
    }

    /**
     * Called on every request to decide if this authenticator should be
     * used for the request. Returning `false` will cause this authenticator
     * to be skipped.
     */
    public function supports(Request $request): ?bool
    {
        return $request->isMethod('POST');
    }

    /**
     * @throws JsonException
     */
    public function authenticate(Request $request): Passport
    {
        $credentials = [
            'username' => json_decode($request->getContent(), false, 512, JSON_THROW_ON_ERROR)->username,
            'password' => json_decode($request->getContent(), false, 512, JSON_THROW_ON_ERROR)->password
        ];
        return new SelfValidatingPassport(new UserBadge($credentials['username']));
    }

    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);
    }
}
class FirebaseUserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
    private Auth $auth;

    /**
     * @param Auth $auth
     */
    public function __construct(Auth $auth)
    {
        $this->auth = $auth;
    }

    /**
     * Symfony calls this method if you use features like switch_user
     * or remember_me. If you're not using these features, you do not
     * need to implement this method.
     *
     * @param string $identifier
     * @return UserInterface
     * @throws AuthException
     * @throws FirebaseException
     */
    public function loadUserByIdentifier(string $identifier): UserInterface
    {
        $user = $this->auth->getUserByEmail($identifier);
        return new FirebaseUser(
            $user->uid ?? '',
            $user->email ?? '',
            $user->passwordHash ?? '',
            $user->displayName ?? ''
        );
    }

    /**
     * Refreshes the user after being reloaded from the session.
     *
     * When a user is logged in, at the beginning of each request, the
     * User object is loaded from the session and then this method is
     * called. Your job is to make sure the user's data is still fresh by,
     * for example, re-querying for fresh User data.
     *
     * If your firewall is "stateless: true" (for a pure API), this
     * method is not called.
     *
     * @param UserInterface $user
     * @return UserInterface
     */
    public function refreshUser(UserInterface $user): UserInterface
    {
        if (!$user instanceof FirebaseUser) {
            throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
        }
        // Return a User object after making sure its data is "fresh".
        // Or throw a UserNotFoundException if the user no longer exists.
        throw new RuntimeException('TODO: fill in refreshUser() inside '.__FILE__);
    }

    /**
    * Tells Symfony to use this provider for this User class.
    */
    public function supportsClass(string $class): bool
    {
        return FirebaseUser::class === $class || is_subclass_of($class, FirebaseUser::class);
    }

    /**
    * Upgrades the hashed password of a user, typically for using a better hash algorithm.
    */
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
    {
        if (!$user instanceof FirebaseUser) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
        }
        $user->setPasswordHash($newHashedPassword);

    // TODO: when hashed passwords are in use, this method should:
    // 1. persist the new password in the user storage
    // 2. update the $user object with $user->setPassword($newHashedPassword);
    }
}
class FirebaseUser implements UserInterface, PasswordAuthenticatedUserInterface
{
    #[ApiProperty(
        identifier: true
    )]
    private ?string $uid;
    #[ApiProperty(
        description: 'Email de l\'utilisateur provenant de Firebase'
    )]
    private ?string $email;
    #[ApiProperty(
        description: 'Mot de passe de l\'utilisateur Firebase'
    )]
    private ?string $passwordHash;
    #[ApiProperty(
        description: 'Nom de l\'utilisateur provenant de Firebase'
    )]
    private ?string $displayName;

    /**
     * @param string|null $uid
     * @param string|null $email
     * @param string|null $passwordHash
     * @param string|null $displayName
     */
    public function __construct(?string $uid, ?string $email, ?string $passwordHash, ?string $displayName)
    {
        $this->uid = $uid;
        $this->email = $email;
        $this->passwordHash = $passwordHash;
        $this->displayName = $displayName;
    }

    /**
     * @return string|null
     */
    public function getUid(): ?string
    {
        return $this->uid;
    }

    /**
     * @param string|null $uid
     */
    public function setUid(?string $uid): void
    {
        $this->uid = $uid;
    }

    /**
     * @return string|null
     */
    public function getEmail(): ?string
    {
        return $this->email;
    }

    /**
     * @param string|null $email
     */
    public function setEmail(?string $email): void
    {
        $this->email = $email;
    }

    /**
     * @return string|null
     */
    public function getPasswordHash(): ?string
    {
        return $this->passwordHash;
    }

    /**
     * @param string|null $passwordHash
     */
    public function setPasswordHash(?string $passwordHash): void
    {
        $this->passwordHash = $passwordHash;
    }

    /**
     * @return string|null
     */
    public function getDisplayName(): ?string
    {
        return $this->displayName;
    }

    /**
     * @param string|null $displayName
     */
    public function setDisplayName(?string $displayName): void
    {
        $this->displayName = $displayName;
    }

    /**
     * @return string[]
     */
    public function getRoles(): array
    {
        return ['ROLE_USER'];
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials(): void
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    /**
     * A visual identifier that represents this user.
     * @see UserInterface
     */
    public function getUserIdentifier(): string
    {
        return (string) $this->email;
    }

    public function getPassword(): ?string
    {
        // passwordHash provide firebase
        return $this->passwordHash;
    }
}

几天后我回来。 虽然知道如何通过 JWT 中的 API 平台与来自 Firebase 的用户进行身份验证会很有趣,但我在这里分享我的想法,因为我已经改变了我的情况。

其实我分析得还不够。 虽然我喜欢 ApiPlatform,但我意识到我必须删除它并使我的 Symfony 应用程序成为连接到 Firebase SDK 的非常经典的 BackOffice。 所以我有 2 个应用程序(Symfony/Flutter)链接到 1 个后端(Firebase、Auth+CloudFirestore),合乎逻辑。

因此,无需使用额外的 API 进行额外的身份验证,这只会使事情变得更重并使我的目标复杂化。 有时把事情做好是件好事。

总是在没有 doctrine 的情况下对验证器有所了解。 给你力量。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM