0

我使用 jwt 令牌进行身份验证,我不得不更改user_identity_field为电子邮件。之后,当我尝试打电话时,/api/token/refresh我有 401 状态码。因为username属性中的刷新令牌实体保存了用户的用户名数据

我的配置

lexik_jwt_authentication:
private_key_path: '%jwt_private_key_path%'
public_key_path:  '%jwt_public_key_path%'
pass_phrase:      '%jwt_key_pass_phrase%'
token_ttl:        '%jwt_token_ttl%'
user_identity_field: email

gesdinet_jwt_refresh_token:
ttl: '%jwt_refresh_token_ttl%'
ttl_update: true
user_provider: security.user.provider.concrete.chain_provider

和我的安全

security:
encoders:
    AppBundle\Entity\User:
        algorithm: bcrypt

    AppBundle\Entity\Admin:
        algorithm: bcrypt

providers:
    chain_provider:
        chain:
            providers: [admins, entity_provider]

    admins:
        entity:
            class: AppBundle:Admin
            property: email

    entity_provider:
        entity:
            class: AppBundle:User
            property: email

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

    refresh:
        pattern:  ^/api/token/refresh
        stateless: true
        anonymous: true

    api_admin:
        pattern:   ^/api/admin
        stateless: true
        anonymous: false
        provider: chain_provider
        guard:
            authenticators:
                - app.jwt_token_authenticator

    login:
        pattern:  ^/api/login
        stateless: true
        anonymous: true
        form_login:
            check_path: /api/login_check
            require_previous_session: false
            username_parameter: _email
            password_parameter: _password
            success_handler: custom
            failure_handler: lexik_jwt_authentication.handler.authentication_failure

现在/api/token/refresh我有回应

{
  "code": 401,
  "message": "Bad credentials"
}

因为\Gesdinet\JWTRefreshTokenBundle\Entity\RefreshTokenusername来自用户的数据,但在我的配置中lexik_jwt_authentication我改变了它

user_identity_field: email

如何申请user_identity_field: email刷新令牌?

4

1 回答 1

0

首先请阅读这篇文章:http ://symfony.com/doc/3.3/bundles/override.html#services-configuration和这篇文章:http: //symfony.com/doc/3.3/service_container/compiler_passes.html

这是我为几乎相同的问题所做的(我需要用户 ID 而不是用户名)

// config.yml
lexik_jwt_authentication:
    private_key_path: '%jwt_private_key_path%'
    public_key_path:  '%jwt_public_key_path%'
    pass_phrase:      '%jwt_key_pass_phrase%'
    token_ttl:        '%jwt_token_ttl%'
    user_identity_field: 'id'

    # token extraction settings
    token_extractors:
        authorization_header:      # look for a token as Authorization Header
            enabled: true
            prefix:  ~
            name: 'Authorization'

gesdinet_jwt_refresh_token:
    ttl: '%jwt_refresh_token_ttl%'
    ttl_update: true
    firewall: 'api_secured'
    user_provider: 'security.db_user_provider' # NOTE THIS

防火墙设置:

security:
    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email
        db_provider:
            id: security.db_user_provider
    firewalls:
        refresh:
            pattern: ^/api/v1/token/refresh
            stateless: true
            anonymous: true
        api_login:
            pattern: ^/api/v1/user/login
            stateless: true
            anonymous: true
            provider: db_provider
            json_login:
                check_path: /api/v1/user/login
                success_handler: lexik_jwt_authentication.handler.authentication_success
                failure_handler: lexik_jwt_authentication.handler.authentication_failure
                require_previous_session: false
        api_secured:
            pattern: <removed>
            stateless: true
            anonymous: false
            provider: db_provider
            guard:
                authenticators:
                    - lexik_jwt_authentication.jwt_token_authenticator

服务定义:

// Application/UserBundle/Resources/config/services.yml
security.db_user_provider:
    class: Application\UserBundle\Util\Security\DB\UserProvider
    calls:
        - ['setDoctrine', ['@doctrine']]

security.jwtrefreshtoken.send_token:
    class: Application\UserBundle\EventListener\OverrideAttachRefreshTokenOnSuccessListener
    arguments: [ "@gesdinet.jwtrefreshtoken.refresh_token_manager", "%gesdinet_jwt_refresh_token.ttl%", "@validator", "@request_stack" ]

用户提供者类:

// Application/UserBundle/Util/Security/DB/UserProvider.php

class UserProvider implements UserProviderInterface
{
    /**
     * @var AbstractManagerRegistry
     */
    protected $doctrine;

    /**
     * @param AbstractManagerRegistry $doctrine
     *
     * @return $this
     */
    final public function setDoctrine(AbstractManagerRegistry $doctrine)
    {
        $this->doctrine = $doctrine;

        return $this;
    }

    /**
     * Loads the user for the given username.
     *
     * This method must throw UsernameNotFoundException if the user is not
     * found.
     *
     * @param string $username The username
     *
     * @return UserInterface
     *
     * @throws UsernameNotFoundException if the user is not found
     */
    public function loadUserByUsername($username)
    {
        // I need these 3 options because I use FOSUserBundle
        if (is_numeric($username)) {
            $user = $this->doctrine->getManager()->getRepository(User::class)->find($username);
        } elseif (filter_var($username, FILTER_VALIDATE_EMAIL)) {
            $user = $this->doctrine->getManager()->getRepository(User::class)->findOneBy(['email' => $username]);
        } else {
            $user = $this->doctrine->getManager()->getRepository(User::class)->findOneBy(['username' => $username]);
        }

        if (!$user) {
            throw new UsernameNotFoundException();
        }

        return $user;
    }

    /**
     * Refreshes the user.
     *
     * It is up to the implementation to decide if the user data should be
     * totally reloaded (e.g. from the database), or if the UserInterface
     * object can just be merged into some internal array of users / identity
     * map.
     *
     * @param UserInterface $user
     *
     * @return UserInterface
     *
     * @throws UnsupportedUserException if the user is not supported
     */
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(
                sprintf('Instances of "%s" are not supported.', get_class($user))
            );
        }

        return $this->loadUserByUsername($user->getUsername());
    }

    /**
     * Whether this provider supports the given user class.
     *
     * @param string $class
     *
     * @return bool
     */
    public function supportsClass($class)
    {
        return User::class === $class;
    }
}

这是主要部分:

// Application/UserBundle/EventListener/OverrideAttachRefreshTokenOnSuccessListener.php

namespace Application\UserBundle\EventListener;

use Gesdinet\JWTRefreshTokenBundle\EventListener\AttachRefreshTokenOnSuccessListener;
use Gesdinet\JWTRefreshTokenBundle\Request\RequestRefreshToken;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Symfony\Component\Security\Core\User\UserInterface;

class OverrideAttachRefreshTokenOnSuccessListener extends AttachRefreshTokenOnSuccessListener
{
    /**
     * @param AuthenticationSuccessEvent $event
     */
    public function attachRefreshToken(AuthenticationSuccessEvent $event)
    {
        $data = $event->getData();
        $user = $event->getUser();
        $request = $this->requestStack->getCurrentRequest();

        if (!$user instanceof UserInterface) {
            return;
        }

        $refreshTokenString = RequestRefreshToken::getRefreshToken($request);

        if ($refreshTokenString) {
            $data['refresh_token'] = $refreshTokenString;
        } else {
            $datetime = new \DateTime();
            $datetime->modify('+'.$this->ttl.' seconds');

            $refreshToken = $this->refreshTokenManager->create();
            $refreshToken->setUsername($user->getId()); // Change this to $user->getEmail() for your purpose
            $refreshToken->setRefreshToken();
            $refreshToken->setValid($datetime);

            $valid = false;
            while (false === $valid) {
                $valid = true;
                $errors = $this->validator->validate($refreshToken);
                if ($errors->count() > 0) {
                    foreach ($errors as $error) {
                        if ('refreshToken' === $error->getPropertyPath()) {
                            $valid = false;
                            $refreshToken->setRefreshToken();
                        }
                    }
                }
            }

            $this->refreshTokenManager->save($refreshToken);
            $data['refresh_token'] = $refreshToken->getRefreshToken();
        }

        $event->setData($data);
    }
}

创建新的编译器:

// Application/UserBundle/DependencyInjection/Compiler/OverrideAttachRefreshTokenOnSuccessListenerCompiler.php

class OverrideAttachRefreshTokenOnSuccessListenerCompiler implements CompilerPassInterface
{
    /**
     * @param ContainerBuilder $container
     */
    public function process(ContainerBuilder $container)
    {
        $definition = $container->getDefinition('gesdinet.jwtrefreshtoken.send_token');
        $definition->setClass(OverrideAttachRefreshTokenOnSuccessListener::class);
    }
}

最后一件事 - 注册你的编译器:

class ApplicationUserBundle extends Bundle
{
    /**
     * @param ContainerBuilder $container
     */
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $container
            ->addCompilerPass(new OverrideAttachRefreshTokenOnSuccessListenerCompiler());
    }
}
于 2018-03-29T10:19:38.293 回答