"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'
      ...