我有一个symfony4应用程序,并且正在使用knpuniversity / oauth2-client-bundle来根据不和谐的oAuth端点对用户进行身份验证。
用户单击“通过Discord登录”按钮,他会看到discord的授权页面,接受该页面,现在已登录到我的页面上。
到目前为止一切顺利。
这是行不通的:
用户在另一台计算机上,因此我的页面上没有会话。他登录到不和谐的Web客户端。之后,他访问了我的页面,但是他没有登录。
当他再次单击“通过Discord登录”时,他会再次看到授权页面,而不是直接在我的页面上登录。
也许我在这里出错了,但是通常当我在google,facebook或其他任何东西上使用oAuth登录时,就像在stackoverflow上一样,我再也看不到授权页面。我单击“使用XY登录”,并且只要登录到相应的帐户,我也将立即在另一页上登录。
<?php
namespace App\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\OAuth2Client;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use League\OAuth2\Client\Token\AccessToken;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class DiscordAuthenticator extends SocialAuthenticator
{
/**
* @var \KnpU\OAuth2ClientBundle\Client\ClientRegistry
*/
private $clientRegistry;
/**
* @var \App\Repository\UserRepository
*/
private $repository;
/**
* @var \Symfony\Component\Routing\RouterInterface
*/
private $router;
/**
* DiscordAuthenticator constructor.
*
* @param \KnpU\OAuth2ClientBundle\Client\ClientRegistry $clientRegistry
* @param \App\Repository\UserRepository $repository
* @param \Symfony\Component\Routing\RouterInterface $router
*/
public function __construct(
ClientRegistry $clientRegistry,
UserRepository $repository,
RouterInterface $router
) {
$this->clientRegistry = $clientRegistry;
$this->repository = $repository;
$this->router = $router;
}
/**
* @inheritDoc
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse('/login/', Response::HTTP_TEMPORARY_REDIRECT);
}
/**
* @inheritDoc
*/
public function supports(Request $request): bool
{
return $request->attributes->get('_route') === 'discord_set_token';
}
/**
* @inheritDoc
*/
public function getCredentials(Request $request)
{
return $this->fetchAccessToken($this->getDiscordClient());
}
/**
* @inheritDoc
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
try {
/** @var \Wohali\OAuth2\Client\Provider\DiscordResourceOwner $discordUser */
$discordUser = $this->getDiscordClient()
->fetchUserFromToken($credentials);
$email = $discordUser->getEmail();
$existingUser = $this->repository->findOneBy(
[
'externalId' => $discordUser->getId(),
'externalIdSource' => 'discord',
]
);
if ($existingUser) {
return $existingUser;
}
$user = $this->repository->findOneBy(['email' => $email]);
if (!$user) {
$user = new User(
$discordUser->getId(),
'discord',
$discordUser->getUsername(),
$discordUser->getEmail()
);
$user->setExternalId($discordUser->getId());
$user->setExternalIdSource('discord');
$user->setToken($credentials->getToken());
$user->setRefreshToken($credentials->getRefreshToken());
$user->setTokenExpiresFromTimestamp($credentials->getExpires());
}
$this->repository->persist($user);
return $user;
} catch (\Throwable $e) {
throw new AuthenticationException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* @inheritDoc
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): Response
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
/**
* @inheritDoc
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): Response
{
$targetUrl = $this->router->generate('home');
return new RedirectResponse($targetUrl);
}
/**
* @return \KnpU\OAuth2ClientBundle\Client\OAuth2Client
*/
private function getDiscordClient(): OAuth2Client
{
return $this->clientRegistry
->getClient('discord');
}
}
<?php
namespace App\Security;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class UserProvider implements UserProviderInterface
{
/**
* @var \App\Repository\UserRepository
*/
private $repository;
/**
* UserProvider constructor.
*
* @param \App\Repository\UserRepository $repository
*/
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
/**
* @inheritDoc
*/
public function loadUserByUsername($username): UserInterface
{
$user = $this->repository->findOneBy(['email' => $username]);
if (!$user) {
throw new UsernameNotFoundException(sprintf('No User with username %s found', $username));
}
return $user;
}
/**
* @inheritDoc
*/
public function refreshUser(UserInterface $user): UserInterface
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
// TODO find out how to use the refresh token to get a new one
return $user;
}
/**
* @inheritDoc
*/
public function supportsClass($class): bool
{
return $class === User::class;
}
}
security:
providers:
user_provider:
entity:
class: App:User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
logout:
path: /logout
target: /login
guard:
authenticators:
- App\Security\DiscordAuthenticator
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManager;
/**
* Class UserRepository
*/
class UserRepository
{
/**
* @var \Doctrine\ORM\EntityManager
*/
private $entityManager;
/**
* @var \Doctrine\ORM\EntityRepository
*/
private $repository;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $entityManager->getRepository(User::class);
}
/**
* Finds an entity by its primary key / identifier.
*
* @param int $id
*
* @return \App\Entity\User|null
*/
public function find(int $id): ?User
{
return $this->repository->find($id);
}
/**
* Finds all entities in the repository.
*
* @return \App\Entity\User[]
*/
public function findAll(): iterable
{
return $this->repository->findAll();
}
/**
* Finds entities by a set of criteria.
*
* @param array $criteria
* @param array|null $orderBy
* @param int|null $limit
* @param int|null $offset
*
* @return \App\Entity\User[]
*/
public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null): iterable
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
/**
* Finds a single entity by a set of criteria.
*
* @param array $criteria
* @param array|null $orderBy
*
* @return \App\Entity\User|null
*/
public function findOneBy(array $criteria, array $orderBy = null): ?User
{
return $this->repository->findOneBy($criteria, $orderBy);
}
/**
* Counts entities by a set of criteria.
*
* @param array $criteria
*
* @return int
*/
public function count(array $criteria): int
{
return $this->repository->count($criteria);
}
/**
* Select all elements from a selectable that match the expression and
* return a new collection containing these elements.
*
* @param \Doctrine\Common\Collections\Criteria $criteria
*
* @return \App\Entity\User[]
*/
public function matching(Criteria $criteria): iterable
{
return $this->repository->matching($criteria);
}
/**
* @param \App\Entity\User $user
*
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function persist(User $user): void
{
$this->entityManager->merge($user);
$this->entityManager->flush();
}
}
<?php
namespace App\Controller;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route(path="discord/")
*/
class DiscordOAuthController extends AbstractController
{
/**
* @var \KnpU\OAuth2ClientBundle\Client\ClientRegistry
*/
private $clientRegistry;
/**
* DiscordOAuthController constructor.
*
* @param \KnpU\OAuth2ClientBundle\Client\ClientRegistry $clientRegistry
*/
public function __construct(ClientRegistry $clientRegistry)
{
$this->clientRegistry = $clientRegistry;
}
/**
* @Route(name="discord_redirect_authorization", path="redirect_authorization")
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function redirectAuthorizationAction(): Response
{
return $this->clientRegistry->getClient('discord')
->redirect(['identify', 'email', 'guilds']);
}
/**
* @Route(name="discord_set_token", path="set_token")
*
* @return void
*/
public function setTokenAction(): void
{
// empty as authenticator will handle the request
}
}
答案 0 :(得分:0)
在我从事这项工作的时候,我没有太多时间,只是简要地浏览了文档。
现在我有更多时间,花一些时间阅读Discords oAuth的整个文档。
然后我发现查询参数prompt
,将其设置为none
时只会请求一次授权,否则将直接重定向到redirect_uri