首先请阅读这篇文章: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());
}
}