简体   繁体   English

使用 Symfony 5.3 检查 JWT (Firebase) 令牌

[英]Check JWT (Firebase) Token with Symfony 5.3

I'm working on an API, and I had implemented a JWT to make it stateless.我正在开发一个 API,并且我已经实现了一个 JWT 以使其无状态。 I created an AuthController, which returns a JWT when login information is correct.我创建了一个 AuthController,它在登录信息正确时返回一个 JWT。 Here you can see the return code that generates the token:在这里您可以看到生成令牌的返回码:

/* RETURN MESSAGE */
$body = [
    'auth_token' => $jwt,
];
$json = new JsonResponse($body);
$json->setStatusCode(201, "Created");   // Headers
return $json;

This is the result when I run the authenticate method, un the URL localhost:8000/authenticate .这是我在 URL localhost:8000/authenticate运行身份验证方法时的结果。
Now, what I would need to do is that, when a user tries to get another / URL, the program doesn't allow him to reach it if he's not passing the Bearer token in the request's header.现在,我需要做的是,当用户尝试获取另一个/ URL 时,如果他没有在请求的标头中传递 Bearer 令牌,则程序不允许他访问它。 But it's not working.但它不起作用。 The platform always allows me to enter any URL without setting an Authorization in the header.该平台始终允许我输入任何 URL,而无需在标题中设置授权。
Here's my security file, where I tried to set this:这是我的安全文件,我尝试在其中进行设置:

security:
    # https://symfony.com/doc/current/security/authenticator_manager.html
    enable_authenticator_manager: true

    # https://symfony.com/doc/current/security.html#c-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'

    encoders:
        App\Entity\ATblUsers:
            algorithm: bcrypt

    providers:
        users_in_memory: { memory: null }

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            # Anonymous property is no longer supported by Symfony. It is commented by now, but it will be deleted in
            # future revision:
            # anonymous: true
            guard:
                authenticators:
                    - App\Security\JwtAuthenticator
            lazy: true
            provider: users_in_memory

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

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true
            stateless: 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: ^/admin, roles: ROLE_ADMIN }
        # - { path: ^/profile, roles: ROLE_USER }

And, finally, here's my App\Security\JwtAuthenticator :最后,这是我的App\Security\JwtAuthenticator

namespace App\Security;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Firebase\JWT\JWT;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;

class JwtAuthenticator extends AbstractGuardAuthenticator
{
    private $em;
    private $params;

    public function __construct(EntityManagerInterface $em, ContainerBagInterface $params)
    {
        $this->em = $em;
        $this->params = $params;
    }

    public function start(Request $request, AuthenticationException $authException = null): JsonResponse
    {
        $body = [
            'message' => 'Authentication Required',
        ];
        return new JsonResponse($body, Response::HTTP_UNAUTHORIZED);
    }

    public function supports(Request $request): bool
    {
        return $request->headers->has('Authorization');
    }

    public function getCredentials(Request $request)
    {
        return $request->headers->get('Authorization');
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        try{
            $credentials = str_replace('Bearer ', '', $credentials);
            $jwt = (array) JWT::decode($credentials, $this->params->get('jwt_secret'), ['HS256']);
            return $this->em->getRepository('App:ATblUsers')->find($jwt['sub']);
        }catch (\Exception $exception){
            throw new AuthenticationException($exception->getMessage());
        }

    }

    public function checkCredentials($credentials, UserInterface $user)
    {

    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
    {
        return new JsonResponse([
            'message' => $exception->getMessage()
        ], Response::HTTP_UNAUTHORIZED);
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
    {
        return;
    }

    public function supportsRememberMe(): bool
    {
        return false;
    }
}

I've been looking at a lot of websites and tutorials, but not anyone is doing exactly what I need or are implementing very basic functionalities that don't match with what I need.我一直在看很多网站和教程,但没有人在做我需要的,或者正在实现与我需要的不匹配的非常基本的功能。 Almost all of that websites explain this using Symfony 4, but I'm using Symfony 5, so a lot of functions that use in tutorials are deprecated.几乎所有这些网站都使用 Symfony 4 来解释这一点,但我使用的是 Symfony 5,因此教程中使用的许多功能都已弃用。 Does someone know what I am missing?有人知道我错过了什么吗?

You are probably missing access_control configuration in security.yaml :您可能在security.yaml中缺少access_control配置:

security:
    # https://symfony.com/doc/current/security/authenticator_manager.html
    enable_authenticator_manager: true

    # https://symfony.com/doc/current/security.html#c-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'

    encoders:
        App\Entity\ATblUsers:
            algorithm: bcrypt

    providers:
        users_in_memory: { memory: null }

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            # Anonymous property is no longer supported by Symfony. It is commented by now, but it will be deleted in
            # future revision:
            # anonymous: true
            guard:
                authenticators:
                    - App\Security\JwtAuthenticator
            lazy: true
            provider: users_in_memory

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

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true
            stateless: 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: ^/authenticate, roles: PUBLIC_ACCESS }
        - { path: ^/, roles: IS_AUTHENTICATED_FULLY }

I have not looked at your code in detail, I just want to tell you that what you are doing hard already exists in a bundle maintained and well documented and you will not need to hardcode, I really invite you to use it is very useful.我没有详细查看您的代码,我只想告诉您,您正在做的事情已经存在于一个维护和记录良好的包中,您不需要硬编码,我真的邀请您使用它非常有用。

https://github.com/lexik/LexikJWTAuthenticationBundle https://github.com/lexik/LexikJWTAuthenticationBundle

Solution: Symfony 6解决方案:Symfony 6

In my case, I came here looking for a Symfony 6 solution.就我而言,我来这里是为了寻找 Symfony 6 的解决方案。

I had to install Firebase PHP SDK (composer require kreait/firebase-php)我必须安装 Firebase PHP SDK(作曲家需要 kreait/firebase-php)

I had to download authentication json file from firebase (Project configuration -> Service accounts) in order to init Firebase SDK我必须从firebase(项目配置->服务帐户)下载身份验证json文件才能初始化Firebase SDK

App\Security\JWTAuthenticator:应用\安全\JWTAuthenticator:

<?php
    namespace App\Security;
    
    use App\Entity\User;
    use Doctrine\ORM\EntityManagerInterface;
    use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
    use Symfony\Component\HttpFoundation\JsonResponse;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Security\Core\Exception\AuthenticationException;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    use Firebase\JWT\JWT;
    use Firebase\JWT\Key;
    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;
    use Symfony\Component\Cache\Simple\FilesystemCache;
    use Kreait\Firebase;
    use Firebase\Auth\Token\Exception\InvalidToken;
    
    
    class JWTAuthenticator extends AbstractAuthenticator
    {
        private $em;
        private $params;
        private $projectDirectory;
        private $firebase;
    
        public function __construct(string $projectDirectory, EntityManagerInterface $em, ContainerBagInterface $params)
        {
            $this->projectDirectory = $projectDirectory;
            $this->em = $em;
            $this->params = $params;
            $this->firebase = (new Firebase\Factory())->withServiceAccount($this->projectDirectory.'/firebase-authentication.json');
        }
    
        public function start(Request $request, AuthenticationException $authException = null): JsonResponse
        {
            $body = [
                'message' => 'Authentication Required',
            ];
            return new JsonResponse($body, Response::HTTP_UNAUTHORIZED);
        }
    
        public function supports(Request $request): bool
        {
            return $request->headers->has('Authorization');
        }
    
        public function getCredentials(Request $request)
        {
            return $request->headers->get('Authorization');
        }
    
        public function onAuthenticationFailure(Request $request, AuthenticationException $exception): JsonResponse
        {
            return new JsonResponse([
                'message' => $exception->getMessage()
            ], Response::HTTP_UNAUTHORIZED);
        }
    
        public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey): ?Response
        {
            return null;
        }
    
        public function supportsRememberMe(): bool
        {
            return false;
        }
    
        public function authenticate(Request $request): Passport
        {
            try{
                $credentials = str_replace('Bearer ', '', $this->getCredentials($request));
                $firebaseAuth = $this->firebase->createAuth();
    
                try {
                    $verifiedIdToken = $firebaseAuth->verifyIdToken($credentials);
                    $tokenClaims = $verifiedIdToken->claims();

                    $sub = $tokenClaims->get('sub');
                    $email = $tokenClaims->get('email');
                    
                    if ($verifiedIdToken->isExpired(new \DateTime())) {
                        throw new AuthenticationException("Token expired.");
                    }
                    
                } catch (Firebase\Exception\AuthException | Firebase\Exception\FirebaseException $e) {
                    throw new AuthenticationException($e->getMessage());
                }
    
            }
            catch (\Exception $exception){
                throw new AuthenticationException($exception->getMessage());
            }
    
            return new SelfValidatingPassport(new UserBadge($email));
        }
    }

My security.yaml我的安全.yaml

security:
    enable_authenticator_manager: true
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
        App\Entity\User:
            algorithm: auto

    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email


    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            lazy: true
            stateless: true
            provider: app_user_provider
            custom_authenticator: App\Security\JWTAuthenticator

    # 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/docs, roles: PUBLIC_ACCESS }
        - { path: ^/, roles: IS_AUTHENTICATED_FULLY }

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

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