[英]Firebase Authenticator JWT with Symfony API Platform
我有一个 Flutter 客户端,它使用 firebase 来创建用户帐户。 用户可以发布到达 web 管理面板的票证,该管理面板由 Symfony 6 和 API 平台构建。
所以我需要2个身份验证器:
我坚持最后一点。 我正在为 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.