【发布时间】:2020-08-25 07:20:39
【问题描述】:
我有一个使用 Lexik JWT v2.8.0、gesdinet jwt 刷新令牌 v0.9.1 和我自己的实体用户的 Symfony 5.1 项目。我可以使用 JWT 登录并获取令牌,将其保存在 HttpOnly cookie 中并成功与受保护的 API 一起使用。
我的网络应用有一些 API Rest,但它也有可供浏览的页面。所以我的意图是通过 API 完成这一切,但在用户获得令牌时也登录 Web 浏览器。
但是由于 api 登录是无状态的,所以在成功登录后,web profiler 仍然显示为 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, roles: IS_AUTHENTICATED_ANONYMOUSLY, 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 bundle的默认认证码。
在 BEARER cookie 中以正确的格式存储 jwt,如下所示:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1OTgzNTE0ODYsImV4cCI6MTU5ODM1NTA4Niwicm9sZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJlbWFpbCI6ImFkbWluQGVtYWlsLmNvbSJ9.p5WYCxEE-VSxp09Eo7CxXoxi6zy1ZcnLJiBe1YGsrk3iFm7T-6JAWbvyb9ZW_-1jtpYcWQlFOjOf7uET4wRHvlvygnPOeZck7tZM8TUlSqMXIllMeVARz8mEUvXVwhDWEk5T7Ibw9c3VgfvyiLUgSb_cmSyK3DwtgPCd9vOYarkag9XKxkxNI9M1OHGL61v1NoQHdkXloC72xdUUMcj5Y8yCJWZFdOp8-Xtfq8ZChAHzwCjXUhC3VnBqZcMT0tAvtilwTGDYDykppNKK1vbNoyOex47wQH_ILEFuX5Eh1p2xfbc0lWm3Ip21z3EQ2M_eOQgZvHR65T3b2dv9g5GPiFp3CNo8AuW8m6rXjWK6NZXJO8qYodxI5cUYYyFooCfVXXU8JXzGQfCZIdOPw-iBGzQEfFuLL50_sAOVcxjklAYCFZYRHwFKWmwl1BwJF4mAw4jnNIAdMmc66Z17ul2Jep9apOO90C1dZzGuVKxWqglc9GZo7-teHt0dMsg0ADrvaOKNJUwDBGJ4lZpWx6_stcl7DkCdc5k1kePGdLa7YXRO3umPwa_FVYVgnT_Z9x7RtfnGioa2TZJCIdbJnuj0L90vkgFBjHqFdVydDaaBu3Y0mKoQ2v3Sf1so4-uwJm8z1vQVZleMQgFibMiyyk3YyDidhPSxxyp4u-4xPNOSDNo P>
如果我没有登录并且没有 jwt,或者我删除了 BEARER cookie,我将无法访问受保护的 API({"code": 401, "message": "JWT Token not found"}),当我分配了 jwt 后,我可以正确请求 API。
【问题讨论】:
-
由于您的工作是无状态的,您需要确保您的令牌已针对每个请求进行验证并解析为正确的用户(身份验证)。这似乎不是这里的情况。所以我假设你看错了地方。请说明您是如何实现实际用户身份验证的,而不是如何从令牌中提取用户。
-
我在每个 api 请求中检查令牌,我认为身份验证是正确的。我需要的是将用户数据传递到网络前端。实际的用户身份验证是由 lexik jwt bundle 完成的,我只是将其更改为将令牌保存在 cookie 中。但现在我将用我的代码编辑我的问题。
-
如果令牌没有返回进行身份验证的用户,但匿名身份验证似乎没有达到您的预期。
-
我添加了我使用的其余代码
-
您仍然只显示第一次身份验证。有趣的部分是您发送下一个请求的位置(添加承载的位置)以及您的后端需要针对正确的用户验证承载的位置。这是您的代码失败或您提供错误输入的地方。
标签: symfony jwt symfony5 lexikjwtauthbundle