如何解决我无法从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 举报,一经查实,本站将立刻删除。