Я использую HWI OAuthBundle в Symfony, чтобы связать их с Google и Facebook. Я использую версию 0.6.3 пакета и версию Symfony v2.8.46.
Итак, моя проблема заключается в следующем: у меня есть пользователь (пользователь A), который уже один раз подключился к своей учетной записи с помощью кнопки Google Connect. Он подключен, на какое-то время закрывает вкладку, возвращается через несколько дней (я использую Remember_me), снова открывает страницу сайта.
OAuthToken
, который он получает во время соединения, содержит правильные данные, но срок его действия истек, а атрибут "authenticated
" OAuthToken имеет значение false.
Поэтому каждый раз, когда я вызываю метод is_granted('ROLE_ADMIN')
в моем шаблоне, метод authenticate
из OAuthProvider
выдает AccountNotLinkedException
.
Вот мое объяснение ниже (+ детали кода)
OAuthProvider выглядит следующим образом
/**
* OAuthProvider.
*
* @author Geoffrey Bachelet <geoffrey.bachelet@gmail.com>
* @author Alexander <iam.asm89@gmail.com>
*/
class OAuthProvider implements AuthenticationProviderInterface
{
/**
* @var ResourceOwnerMapInterface
*/
private $resourceOwnerMap;
/**
* @var OAuthAwareUserProviderInterface
*/
private $userProvider;
/**
* @var UserCheckerInterface
*/
private $userChecker;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @param OAuthAwareUserProviderInterface $userProvider User provider
* @param ResourceOwnerMapInterface $resourceOwnerMap Resource owner map
* @param UserCheckerInterface $userChecker User checker
* @param TokenStorageInterface $tokenStorage
*/
public function __construct(OAuthAwareUserProviderInterface $userProvider, ResourceOwnerMapInterface $resourceOwnerMap, UserCheckerInterface $userChecker, TokenStorageInterface $tokenStorage)
{
$this->userProvider = $userProvider;
$this->resourceOwnerMap = $resourceOwnerMap;
$this->userChecker = $userChecker;
$this->tokenStorage = $tokenStorage;
}
/**
* {@inheritdoc}
*/
public function supports(TokenInterface $token)
{
return
$token instanceof OAuthToken
&& $this->resourceOwnerMap->hasResourceOwnerByName($token->getResourceOwnerName())
;
}
/**
* {@inheritdoc}
*/
public function authenticate(TokenInterface $token)
{
if (!$this->supports($token)) {
return;
}
// fix connect to external social very time
if ($token->isAuthenticated()) {
return $token;
}
/* @var OAuthToken $token */
$resourceOwner = $this->resourceOwnerMap->getResourceOwnerByName($token->getResourceOwnerName());
$oldToken = $token->isExpired() ? $this->refreshToken($token, $resourceOwner) : $token;
$userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken());
try {
$user = $this->userProvider->loadUserByOAuthUserResponse($userResponse);
} catch (OAuthAwareExceptionInterface $e) {
$e->setToken($oldToken);
$e->setResourceOwnerName($oldToken->getResourceOwnerName());
throw $e;
}
if (!$user instanceof UserInterface) {
throw new AuthenticationServiceException('loadUserByOAuthUserResponse() must return a UserInterface.');
}
$this->userChecker->checkPreAuth($user);
$this->userChecker->checkPostAuth($user);
$token = new OAuthToken($oldToken->getRawToken(), $user->getRoles());
$token->setResourceOwnerName($resourceOwner->getName());
$token->setUser($user);
$token->setAuthenticated(true);
$token->setRefreshToken($oldToken->getRefreshToken());
$token->setCreatedAt($oldToken->getCreatedAt());
return $token;
}
/**
* @param TokenInterface $expiredToken
* @param ResourceOwnerInterface $resourceOwner
*
* @return OAuthToken|TokenInterface
*/
protected function refreshToken(TokenInterface $expiredToken, ResourceOwnerInterface $resourceOwner)
{
if (!$expiredToken->getRefreshToken()) {
return $expiredToken;
}
$token = new OAuthToken($resourceOwner->refreshAccessToken($expiredToken->getRefreshToken()));
$token->setRefreshToken($expiredToken->getRefreshToken());
$this->tokenStorage->setToken($token);
return $token;
}
}
Моя проблема в том, что OAuthToken, переданный в качестве аргумента методу authenticate
, имеет атрибут $authenticated
, равный false, хотя он имеет правильные пользовательские данные. Как вы можете видеть ниже
Тогда когда метод $userResponse = $resourceOwner->getUserInformation($oldToken->getRawToken());
, мой userResponse выглядит так
Как вы можете видеть в атрибуте $data
UserResponse, есть некоторые ошибки. Из-за этих ошибок, когда я звоню $username = $response->getUsername();
, я получаю нулевое значение для $username
. Так что каждый раз, FOSUBUserProvider
бросает и AccountNotLinkedException
из-за этого кода ниже
/**
* {@inheritdoc}
*/
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$username = $response->getUsername();
$user = $this->userManager->findUserBy(array($this->getProperty($response) => $username));
if (null === $user || null === $username) {
throw new AccountNotLinkedException(sprintf("User '%s' not found.", $username));
}
return $user;
}
И это исключение появляется, только если я вызываю is_granted('ROLE_ADMIN')
в моем шаблоне. Если я не использую его, все работает нормально, и я могу получить данные «пользователя A» непосредственно из текущего OAuthToken.
Но как только я использую is_granted
, я получаю это AccountNotLinkedExpetion
.
Чтобы помочь вам, пожалуйста, найдите ниже метод getUserInformation
из GenericOAuth2ResourceOwner
, который используется OAuthProvider
/**
* {@inheritdoc}
*/
public function getUserInformation(array $accessToken, array $extraParameters = array())
{
if ($this->options['use_bearer_authorization']) {
$content = $this->httpRequest(
$this->normalizeUrl($this->options['infos_url'], $extraParameters),
null,
array('Authorization' => 'Bearer '.$accessToken['access_token'])
);
} else {
$content = $this->doGetUserInformationRequest(
$this->normalizeUrl(
$this->options['infos_url'],
array_merge(array($this->options['attr_name'] => $accessToken['access_token']), $extraParameters)
)
);
}
$response = $this->getUserResponse();
$response->setData($content instanceof ResponseInterface ? (string) $content->getBody() : $content);
$response->setResourceOwner($this);
$response->setOAuthToken(new OAuthToken($accessToken));
return $response;
}
А также данные отрисованного UserResponse
{\n
"error": {\n
"code": 401,\n
"message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See ▶
"status": "UNAUTHENTICATED"\n
}\n
}\n
"""
Как получился ответ пользователя с ошибками? Это потому, что OAuthToken не аутентифицирован? В таком случае как это предотвратить?
Когда "пользователь A" возвращается на страницу, если OAuthToken не аутентифицирован, я бы предпочел, чтобы пользователь просто отключился, а не имел исключение (= 500 ошибка сервера).
Кто-нибудь может мне помочь, пожалуйста? Спасибо?
ОБНОВЛЕНИЕ
Так что у меня есть дополнительная информация. Я частично знаю, откуда и откуда возникла ошибка. Так в GenericOAuth2ResourceOwner
есть такой метод
public function getUserInformation(array $accessToken, array $extraParameters = array())
{
if ($this->options['use_bearer_authorization']) {
$content = $this->httpRequest(
$this->normalizeUrl($this->options['infos_url'], $extraParameters),
null,
array('Authorization' => 'Bearer '.$accessToken['access_token'])
);
} else {
$content = $this->doGetUserInformationRequest(
$this->normalizeUrl(
$this->options['infos_url'],
array_merge(array($this->options['attr_name'] => $accessToken['access_token']), $extraParameters)
)
);
}
$response = $this->getUserResponse();
$response->setData($content instanceof ResponseInterface ? (string) $content->getBody() : $content);
$response->setResourceOwner($this);
$response->setOAuthToken(new OAuthToken($accessToken));
return $response;
}
Похоже, что для возвращающихся пользователей $accessToken['access_token']
не обновляется и, следовательно, существует проблема с извлеченными $content
.
На странице, где у меня была ошибка, $content
выглядело так
Но когда я открыл новое окно в режиме инкогнито и попытался подключиться к Google с тем же пользователем, $content
выглядел так
Единственная разница между этими двумя значениями $accessToken['access_token']
.
Моя гипотеза состоит в том, что для пользователей Remember_me старое значение сохраняется для $accessToken['access_token']
.
Как вы думаете, это так? Есть ли способ автоматически обновить значение access_token? Если нет, как я могу вместо этого отключить пользователя?