微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

我无法从JWT

如何解决我无法从JWT

我有一个使用Lexik JWT v2.8.0,gesdinet jwt刷新令牌v0.9.1和我自己的实体用户的Symfony 5.1项目。我可以使用JWT登录获取令牌,将其保存在HttpOnly cookie中并成功将其与受保护的API结合使用。

我的Web应用程序有一些API Rest,但也有要浏览的页面。因此,我的目的是通过API完成所有操作,而且还可以在用户获得令牌时登录Web浏览器。

但是由于api登录是无状态的,因此成功登录后,Web事件探查器仍显示为anon登录。而且我无法从令牌中获得用户。我提供了一项服务,以从令牌中获取用户,然后在控制器中对其进行调用,并将一些已登录用户数据发送至前端。

我有this question的服务。我为吸引用户而实施的服务是:

use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use UserBundle\Domain\Entity\User;

class UserService
{
    private TokenStorageInterface $tokenStorage;
    private JWTTokenManagerInterface $jwtManager;

    public function __construct( TokenStorageInterface $storage,JWTTokenManagerInterface $jwtManager )
    {
        $this->tokenStorage = $storage;
        $this->jwtManager = $jwtManager;
    }

    public function getCurrentUser() : ?User
    {
        $decodedJwtToken = $this->jwtManager->decode($this->tokenStorage->getToken());
        if ($decodedJwtToken instanceof TokenInterface) {

            $user = $decodedJwtToken->getUser();
            return $user;

        } else {
            return null;
        }
    }
}

它被声明为服务:

get.token.user.service:
    class: UserBundle\Domain\Service\UserService
    arguments: [ '@security.token_storage' ]

但是我从$this->tokenStorage->getToken()获得的只是一个带有“ anon”的网络令牌。用户,因此它不是JWT,并且jwtManager无法对其进行解码。

UserService.PHP on line 25:
Symfony\Component\Security\Core\Authentication\Token\AnonymousToken {#16 ▼
  -secret: "RXVaVx3"
  -user: "anon."
  -roleNames: []
  -authenticated: true
  -attributes: []
}

我还尝试从控制器中的cookie中获取jwt并将其作为参数发送给服务,但是我从解码中得到一个错误,即我传递了一个字符串,并且它期望使用TokenInterface对象,所以它做到了都不起作用。

如何从服务中的令牌中获取用户?是否有通过api登录网络的最佳实践,而不是从jwt获取用户并将其发送到渲染器?

编辑:添加代码以使用lexik jwt并将令牌保存在cookie中:

# /config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
    secret_key: '%env(resolve:JWT_SECRET_KEY)%'
    public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
    pass_phrase: '%env(JWT_PAsspHRASE)%'
    token_ttl: 3600
    user_identity_field: email

token_extractors:
    cookie:
        enabled: true
        name: BEARER

安全文件代码

# /config/packages/security.yaml
security:
    encoders:
        UserBundle\Domain\Entity\User:
            algorithm: auto

    providers:
        app_user_provider:
            entity:
                class: UserBundle\Domain\Entity\User
                property: email
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        login:
            pattern: ^/api/login
            stateless: true
            anonymous: true
            json_login:
                provider: app_user_provider
                check_path: /api/login_check
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
        refresh:
            pattern:  ^/api/token/refresh
            stateless: true
            anonymous: true
        register:
            pattern: ^/api/register
            stateless: true
            anonymous: true
        api:
            pattern: ^/api
            stateless: true
            anonymous: true
            provider: app_user_provider
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
        main:
            anonymous: true
            lazy: true
            provider: app_user_provider

    access_control:
        - { path: ^/api/user,roles: IS_AUTHENTICATED_ANONYMOUSLY,methods: [GET] }
        - { path: ^/api/linkpage,methods: [GET] }
        - { path: ^/api/login,roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api/token/refresh,roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api/register,roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api,roles: IS_AUTHENTICATED_FULLY }

侦听器可将jwt保存在cookie中,并避免令牌出现在响应中:

use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Symfony\Component\HttpFoundation\Cookie;

class AuthenticationSuccessListener
{
    private bool $secure = false;
    private int $tokenTtl;

    public function __construct(int $tokenTtl)
    {
        $this->tokenTtl = $tokenTtl;
    }

    public function onAuthenticationSuccess( AuthenticationSuccessEvent $event)
    {
        $response = $event->getResponse();
        $data = $event->getData();

        $token = $data['token'];
        unset($data['token']);  // remove token from response. It works.
        unset($data['refresh_token']); // remove token from refresh token,even though I still get this token in the response
        $event->setData($data);

        $response->headers->setCookie(
            new Cookie(
                'BEARER',$token,(new \DateTime())->add(new \DateInterval('PT'. $this->tokenTtl . 'S')),'/',null,$this->secure
            )
        );
    }
}


#services.yaml
app.listener.authenticationsuccesslistener:
    class: UserBundle\Application\Listeners\AuthenticationSuccessListener
    arguments: ['%lexik_jwt_authentication.token_ttl%']
    tags:
      - { name: kernel.event_listener,event: lexik_jwt_authentication.on_authentication_success,method: onAuthenticationSuccess }

我仍然为刷新令牌编码了2个侦听器,但我认为不需要它们。

其余的身份验证是lexik jwt捆绑包中的认身份验证代码

在BEARER cookie中以正确的格式存储jwt,如下所示:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1OTgzNTE0ODYsImV4cCI6MTU5ODM1NTA4Niwicm9sZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJlbWFpbCI6ImFkbWluQGVtYWlsLmNvbSJ9.p5WYCxEE-VSxp09Eo7CxXoxi6zy1ZcnLjibe1YGsrk3iFm7T-6JAWbvyb9ZW_-1jtpYcWQlFOjOf7uET4wRHvlvygnPOeZck7tZM8TUlSqMXIllMeVARz8mEUvXVwhDWEk5T7Ibw9c3VgfvyiLUgSb_cmSyK3DwtgPCd9vOYarkag9XKxkxNI9M1OHGL61v1NoQHdkXloC72xdUUMcj5Y8yCJWZFdOp8-Xtfq8ZChAHzwCjXUhC3VnBqZcMT0tAvtilwTGDYDykppNKK1vbNoyOex47wQH_ILEFuX5Eh1p2xfbc0lWm3Ip21z3EQ2M_eOQgZvHR65T3b2dv9g5GPiFp3CNo8AuW8m6rXjWK6NZXJO8qYodxI5cUYYyFooCfVXXU8JXzGQfCZIdOPw-iBGzQEfFuLL50_sAOVcxjklAYCFZYRHwFKWmwl1BwJF4mAw4jnNIAdMmc66Z17ul2Jep9apOO90C1dZzGuVKxWqglc9GZo7-teHt0dMsg0ADrvaOKNJUwDBGJ4lZpWx6_stcl7DkCdc5k1kePGdLa7YXRO3umPwa_FVYVgnT_Z9x7RtfnGioa2TZJCIdbJnuj0L90vkgFBjHqFdVydDaaBu3Y0mKoQ2v3Sf1so4-uwJm8z1vQVZleMQgFibMiyyk3YyDidhPSxxyp4u-4xPNOSDNo

如果我没有登录且没有jwt或删除了BEARER cookie,则无法访问受保护的API({“代码”:401,“消息”:“找不到JWT令牌”}),当我分配了jwt时,我可以正确地请求API。

解决方法

问题出在security.yaml配置上,特别是在firewalls.main部分,我必须在其中添加jwt_token_authenticator作为防护。最后,我的security.yaml是:

security:
    encoders:
        UserBundle\Domain\Entity\User:
            algorithm: auto

    providers:
        app_user_provider:
            entity:
                class: UserBundle\Domain\Entity\User
                property: email
        jwt:
            lexik_jwt: ~
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        login:
            pattern: ^/api/login
            stateless: true
            anonymous: true
            json_login:
                provider: app_user_provider
                check_path: /api/login_check
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false
        refresh:
            pattern:  ^/api/token/refresh
            stateless: true
            anonymous: true
        register:
            pattern: ^/api/register
            stateless: true
            anonymous: true
        api:
            pattern: ^/api
            stateless: true
            anonymous: true
            provider: jwt
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
        main:
            anonymous: true
            lazy: true
            provider: jwt
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator
            logout:
                path:   app_logout
                target: /

    access_control:
        - { path: ^/api/login_check,roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api/token/refresh,roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api/register,roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/logout,roles: IS_AUTHENTICATED_FULLY }
        - { path: ^/api,roles: IS_AUTHENTICATED_FULLY }

然后我在注销时遇到了一些问题,因为注销并没有清除jwt cookie,并且注销使我保持登录状态。所以我对LogoutEvent进行了监听,并让监听器删除了cookie。

use Symfony\Component\Security\Http\Event\LogoutEvent;

class LogoutListener
{

    public function onSymfonyComponentSecurityHttpEventLogoutEvent(LogoutEvent $event)
    {
        $response = $event->getResponse();
        $response->headers->clearCookie('BEARER');
        $response->headers->clearCookie('REFRESH_TOKEN');

        $event->setResponse($response);
    }

}

并将其声明为服务:

app.listener.logout:
    class: UserBundle\Application\Listeners\LogoutListener
    tags:
        - name: 'kernel.event_listener'
        event: 'Symfony\Component\Security\Http\Event\LogoutEvent'
        dispatcher: security.event_dispatcher.main

现在,登录名可以在Web上运行,而不必使用我使用的服务对jwt进行解码,并将用户传递给前台。尽管解码jwt的服务现在可以正常工作。

这个问题使我损失了将近一个星期的时间,但最终我找到了解决方案,并且工作正常。因此,如果有人遇到类似问题,我会在此处发布以节省时间。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。