"invalid_grant" 对于 Google 使用 Symfony4 和 knpuniversity OauthBundle 的 SSO
"invalid_grant" for Google SSO with Symfony4 and knpuniversity OauthBundle
当我选择使用 google API 登录我的电子邮件帐户时,我系统地遇到了 "invalid_grant" 错误。
调试 GuardAuthenticator 后,一切正常,直到检索用户(它检索!)但是当我 return 用户时,我陷入 onAuthenticationFailure 并出现 "invalid _grant" 错误。
我正在使用 Symfony 4.1 和 Doctrine ODM:
这是我的代码:
knpu_oauth2_client.yaml :
knpu_oauth2_client:
clients:
google:
# must be "google" - it activates that type!
type: google
# add and configure client_id and client_secret in parameters.yml
client_id: "%env(GOOGLE_CLIENT_ID)%"
client_secret: "%env(GOOGLE_CLIENT_SECRET)%"
# a route name you'll create
redirect_route: connect_google_check
redirect_params: {}
# Optional value for sending access_type parameter. More detail: https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
# access_type: ''
# Optional value for sending hd parameter. More detail: https://developers.google.com/identity/protocols/OpenIDConnect#hd-param
# hosted_domain: ''
# Optional value for additional fields to be requested from the user profile. If set, these values will be included with the defaults. More details: https://developers.google.com/+/web/api/rest/latest/people
# user_fields: {}
# Optional value if you don't want or need to enable Google+ API access.
# use_oidc_mode: false
# whether to check OAuth2 "state": defaults to true
# use_state: true
security.yaml :
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/(?!(api|connect))
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
anonymous: ~
logout:
path: /logout
target: /login
remember_me:
secret: '%env(APP_SECRET)%'
guard:
authenticators:
- App\Security\GuardAuthenticator\GoogleAuthenticator
- App\Security\GuardAuthenticator\LoginFormAuthenticator
- lexik_jwt_authentication.jwt_token_authenticator
entry_point: App\Security\GuardAuthenticator\GoogleAuthenticator
social:
pattern: ^/connect/
stateless: true
anonymous: ~
guard:
authenticators:
- App\Security\GuardAuthenticator\GoogleAuthenticator
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/token/refresh, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/connect/google, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
- { path: ^/api, roles: ROLE_USER }
谷歌身份验证器:
<?php
declare(strict_types=1);
namespace App\Security\GuardAuthenticator;
use App\Document\User;
use Doctrine\ODM\MongoDB\DocumentManager;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use League\OAuth2\Client\Provider\GoogleUser;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Router;
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\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use KnpU\OAuth2ClientBundle\Client\Provider\GoogleClient;
class GoogleAuthenticator extends SocialAuthenticator
{
/**
* @var ClientRegistry
*/
protected $client;
/**
* @var DocumentManager
*/
protected $documentManager;
/**
* @var Router
*/
protected $router;
public function __construct(
ClientRegistry $client,
DocumentManager $documentManager,
RouterInterface $router
) {
$this->client = $client;
$this->documentManager = $documentManager;
$this->router = $router;
}
/**
* @param Request $request The request that resulted in an AuthenticationException
* @param AuthenticationException $authException The exception that started the authentication process
*
* @return Response
*/
public function start(
Request $request,
AuthenticationException $authException = null
) {
return new JsonResponse(['message' => $authException->getMessage()], JsonResponse::HTTP_UNAUTHORIZED);
}
/**
* @param Request $request
*
* @return bool
*/
public function supports(Request $request): bool
{
return $request->attributes->get('_route') === 'connect_google_check';
}
/**
* @param Request $request
*
* @return mixed Any non-null value
*
* @throws \UnexpectedValueException If null is returned
*/
public function getCredentials(Request $request)
{
return $this->fetchAccessToken($this->getGoogleClient());
}
/**
* @param mixed $credentials
* @param UserProviderInterface $userProvider
*
* @throws AuthenticationException
*
* @return UserInterface|null
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
/** @var GoogleUser $googleUser */
$googleUser = $this->getGoogleClient()->fetchUserFromToken($credentials);
$userEmail = $googleUser->getEmail();
$user = $this->documentManager
->getRepository(User::class)
->findOneBy(['email' => $userEmail]);
if ($user) {
return $user;
}
return null;
}
/**
* @param Request $request
* @param AuthenticationException $exception
*
* @return JsonResponse
*/
public function onAuthenticationFailure(
Request $request,
AuthenticationException $exception
): JsonResponse {
return new JsonResponse(
[
'message' => strtr(
$exception->getMessageKey(),
$exception->getMessageData())
],
JsonResponse::HTTP_FORBIDDEN
);
}
/**
* @param Request $request
* @param TokenInterface $token
* @param string $providerKey The provider (i.e. firewall) key
*
* @return Response|null
*/
public function onAuthenticationSuccess(
Request $request,
TokenInterface $token,
$providerKey
) {
$user = $token->getUser();
$googleApiToken = $this->fetchAccessToken($this->getGoogleClient());
$user->setApiToken($googleApiToken);
$this->documentManager->persist($user);
$this->documentManager->flush();
return null;
}
/**
* @return GoogleClient
*/
private function getGoogleClient(): GoogleClient
{
return $this->client->getClient('google');
}
}
GoogleController.php :
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Document\User;
use App\Logger\PlanningLogger;
use Doctrine\ODM\MongoDB\DocumentManager;
use FOS\RestBundle\Controller\Annotations as Rest;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
class GoogleController extends AbstractController
{
/**
* @var ClientRegistry
*/
protected $clientRegistry;
public function __construct(
DocumentManager $documentManager,
LoggerInterface $logger,
ClientRegistry $clientRegistry
) {
parent::__construct($documentManager, $logger);
$this->clientRegistry = $clientRegistry;
}
/**
* @Rest\Get("/connect/google", name="connect_google")
*
* @return RedirectResponse
*/
public function connect()
{
return $this->clientRegistry->getClient('google')->redirect();
}
/**
* @Rest\Get("/connect/google_check", name="connect_google_check")
*
* @param Request $request
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
*/
public function connectCheckAction(Request $request) {}
}
我遵循了文档 oauth2-client-bundle 但我无法解决它。
有人可以帮我解决这个问题吗?
谢谢!
您需要在 knpu_oauth2_client.yaml
中设置 access_type 字段
knpu_oauth2_client:
clients:
google:
.....
access_type: 'offline'
...
当我选择使用 google API 登录我的电子邮件帐户时,我系统地遇到了 "invalid_grant" 错误。 调试 GuardAuthenticator 后,一切正常,直到检索用户(它检索!)但是当我 return 用户时,我陷入 onAuthenticationFailure 并出现 "invalid _grant" 错误。
我正在使用 Symfony 4.1 和 Doctrine ODM:
这是我的代码:
knpu_oauth2_client.yaml :
knpu_oauth2_client:
clients:
google:
# must be "google" - it activates that type!
type: google
# add and configure client_id and client_secret in parameters.yml
client_id: "%env(GOOGLE_CLIENT_ID)%"
client_secret: "%env(GOOGLE_CLIENT_SECRET)%"
# a route name you'll create
redirect_route: connect_google_check
redirect_params: {}
# Optional value for sending access_type parameter. More detail: https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
# access_type: ''
# Optional value for sending hd parameter. More detail: https://developers.google.com/identity/protocols/OpenIDConnect#hd-param
# hosted_domain: ''
# Optional value for additional fields to be requested from the user profile. If set, these values will be included with the defaults. More details: https://developers.google.com/+/web/api/rest/latest/people
# user_fields: {}
# Optional value if you don't want or need to enable Google+ API access.
# use_oidc_mode: false
# whether to check OAuth2 "state": defaults to true
# use_state: true
security.yaml :
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/(?!(api|connect))
form_login:
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
anonymous: ~
logout:
path: /logout
target: /login
remember_me:
secret: '%env(APP_SECRET)%'
guard:
authenticators:
- App\Security\GuardAuthenticator\GoogleAuthenticator
- App\Security\GuardAuthenticator\LoginFormAuthenticator
- lexik_jwt_authentication.jwt_token_authenticator
entry_point: App\Security\GuardAuthenticator\GoogleAuthenticator
social:
pattern: ^/connect/
stateless: true
anonymous: ~
guard:
authenticators:
- App\Security\GuardAuthenticator\GoogleAuthenticator
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/doc, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/token/refresh, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/connect/google, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
- { path: ^/api, roles: ROLE_USER }
谷歌身份验证器:
<?php
declare(strict_types=1);
namespace App\Security\GuardAuthenticator;
use App\Document\User;
use Doctrine\ODM\MongoDB\DocumentManager;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use League\OAuth2\Client\Provider\GoogleUser;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Router;
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\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use KnpU\OAuth2ClientBundle\Client\Provider\GoogleClient;
class GoogleAuthenticator extends SocialAuthenticator
{
/**
* @var ClientRegistry
*/
protected $client;
/**
* @var DocumentManager
*/
protected $documentManager;
/**
* @var Router
*/
protected $router;
public function __construct(
ClientRegistry $client,
DocumentManager $documentManager,
RouterInterface $router
) {
$this->client = $client;
$this->documentManager = $documentManager;
$this->router = $router;
}
/**
* @param Request $request The request that resulted in an AuthenticationException
* @param AuthenticationException $authException The exception that started the authentication process
*
* @return Response
*/
public function start(
Request $request,
AuthenticationException $authException = null
) {
return new JsonResponse(['message' => $authException->getMessage()], JsonResponse::HTTP_UNAUTHORIZED);
}
/**
* @param Request $request
*
* @return bool
*/
public function supports(Request $request): bool
{
return $request->attributes->get('_route') === 'connect_google_check';
}
/**
* @param Request $request
*
* @return mixed Any non-null value
*
* @throws \UnexpectedValueException If null is returned
*/
public function getCredentials(Request $request)
{
return $this->fetchAccessToken($this->getGoogleClient());
}
/**
* @param mixed $credentials
* @param UserProviderInterface $userProvider
*
* @throws AuthenticationException
*
* @return UserInterface|null
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
/** @var GoogleUser $googleUser */
$googleUser = $this->getGoogleClient()->fetchUserFromToken($credentials);
$userEmail = $googleUser->getEmail();
$user = $this->documentManager
->getRepository(User::class)
->findOneBy(['email' => $userEmail]);
if ($user) {
return $user;
}
return null;
}
/**
* @param Request $request
* @param AuthenticationException $exception
*
* @return JsonResponse
*/
public function onAuthenticationFailure(
Request $request,
AuthenticationException $exception
): JsonResponse {
return new JsonResponse(
[
'message' => strtr(
$exception->getMessageKey(),
$exception->getMessageData())
],
JsonResponse::HTTP_FORBIDDEN
);
}
/**
* @param Request $request
* @param TokenInterface $token
* @param string $providerKey The provider (i.e. firewall) key
*
* @return Response|null
*/
public function onAuthenticationSuccess(
Request $request,
TokenInterface $token,
$providerKey
) {
$user = $token->getUser();
$googleApiToken = $this->fetchAccessToken($this->getGoogleClient());
$user->setApiToken($googleApiToken);
$this->documentManager->persist($user);
$this->documentManager->flush();
return null;
}
/**
* @return GoogleClient
*/
private function getGoogleClient(): GoogleClient
{
return $this->client->getClient('google');
}
}
GoogleController.php :
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Document\User;
use App\Logger\PlanningLogger;
use Doctrine\ODM\MongoDB\DocumentManager;
use FOS\RestBundle\Controller\Annotations as Rest;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
class GoogleController extends AbstractController
{
/**
* @var ClientRegistry
*/
protected $clientRegistry;
public function __construct(
DocumentManager $documentManager,
LoggerInterface $logger,
ClientRegistry $clientRegistry
) {
parent::__construct($documentManager, $logger);
$this->clientRegistry = $clientRegistry;
}
/**
* @Rest\Get("/connect/google", name="connect_google")
*
* @return RedirectResponse
*/
public function connect()
{
return $this->clientRegistry->getClient('google')->redirect();
}
/**
* @Rest\Get("/connect/google_check", name="connect_google_check")
*
* @param Request $request
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
*/
public function connectCheckAction(Request $request) {}
}
我遵循了文档 oauth2-client-bundle 但我无法解决它。
有人可以帮我解决这个问题吗?
谢谢!
您需要在 knpu_oauth2_client.yaml
中设置 access_type 字段knpu_oauth2_client:
clients:
google:
.....
access_type: 'offline'
...