Symfony 3 FB登录-在OAuth登录流程后,我回到登录页面,因为身份验证立即过期


Symfony 3 FB Login - After OAuth Login flow I go back at Login page because the authentication expires instantly

我正试图在一个网站上实现Facebook连接。但是,当我通过连接过程时,我返回到登录页面页面,因为身份验证立即过期。我也有一个单独的常规注册和登录present

控制器:

<?php
namespace Vendor'GiftBundle'Controller;
use Sensio'Bundle'FrameworkExtraBundle'Configuration'Route;
use Symfony'Bundle'FrameworkBundle'Controller'Controller;
use Symfony'Component'HttpFoundation'Request;
use Symfony'Component'HttpFoundation'Response;
use Symfony'Component'Routing'Generator'UrlGeneratorInterface;
class FacebookConnectController extends Controller
{
    /**
     * @Route("/connect/facebook", name="connect_facebook")
     */
    public function connectFacebookAction(Request $request)
    {
        // redirect to Facebook
        $facebookOAuthProvider = $this->get('app.facebook_provider');
        $url = $facebookOAuthProvider->getAuthorizationUrl([
            // these are actually the default scopes
            'scope' => ['public_profile', 'email'],
            //'redirect_uri' => [$redir],
        ]);
        return $this->redirect($url);
    }
    /**
     * @Route("/connect/facebook-check", name="connect_facebook_check")
     */
    public function connectFacebookActionCheck()
    {
        // will not be reached!
    }
}

Facebook身份:

<?php
namespace Vendor'GiftBundle'Security;
use Symfony'Component'Security'Guard'AbstractGuardAuthenticator;
use Symfony'Component'HttpFoundation'Request;
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 Symfony'Component'DependencyInjection'ContainerInterface;
use Vendor'GiftBundle'Entity'Logins;
use Symfony'Component'HttpFoundation'RedirectResponse;
use Symfony'Component'HttpFoundation'Response;
use Symfony'Component'Security'Core'Security;
use Symfony'Component'Security'Core'Authentication'Token'UsernamePasswordToken;
class FacebookAuthenticator extends AbstractGuardAuthenticator
{
    private $container;
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }
    public function getCredentials(Request $request)
    {
        if ($request->getPathInfo() != '/connect/facebook-check') {
            // skip authentication unless we're on this URL!
            return null;
        }
        if ($code = $request->query->get('code')) {
            return $code;
        }
        // no code! Something went wrong. Quite probably the user denied our app access
        // you could read the error, error_code, error_description, error_reason query params
        // http://localhost:8000/connect/facebook-check?error=access_denied&error_code=200&error_description=Permissions+error&error_reason=user_denied&state=S2fKgHJSZSJM0Qs2fhKL6USZP50KSBHc#_=_
        throw CustomAuthenticationException::createWithSafeMessage(
            'There was an error getting access from Facebook. Please try again.'
        );
    }
    public function getUser($authorizationCode, UserProviderInterface $userProvider)
    {
        //$user = new Logins();
        $facebookProvider = $this->container->get('app.facebook_provider');
        try {
            // the credentials are really the access token
            $accessToken = $facebookProvider->getAccessToken(
                'authorization_code',
                ['code' => $authorizationCode]
            );
        } catch (IdentityProviderException $e) {
            // probably the authorization code has been used already
            $response = $e->getResponseBody();
            $errorCode = $response['error']['code'];
            $message = $response['error']['message'];
            throw CustomAuthenticationException::createWithSafeMessage(
                'There was an error logging you into Facebook - code '.$errorCode
            );
        }
        /** @var FacebookUser $facebookUser */
        $facebookUser = $facebookProvider->getResourceOwner($accessToken);
        $email = $facebookUser->getEmail();
        //$em = $this->getDoctrine()->getManager();
        //$check = $em->getRepository('VendorGiftBundle:Logins')->findByEmail($email);
        $em = $this->container->get('doctrine')->getManager();
        // 1) have they logged in with Facebook before? Easy!
        $existingUser = $em->getRepository('VendorGiftBundle:Logins')
            ->findOneBy(array('fbid' => $facebookUser->getId()));
        if ($existingUser) {
            return $existingUser;
        }
        // 2) do we have a matching user by email?
        $user = $em->getRepository('VendorGiftBundle:Logins')
                    ->findOneBy(array('email' => $email));
        // 3) no user? Perhaps you just want to create one
        //      or maybe you want to redirect to a registration (in that case, keep reading_
        if (!$user) {
            $user = new Logins();
            $user->setEmail($email);
            $user->setFirstname($facebookUser->getFirstName());
            $user->setLastname($facebookUser->getLastName());
            $user->setCity($facebookUser->getLocale());
            $user->setCreationtime();
            $user->setStatus(1);
            // set an un-encoded password, which basically makes it *not* possible
            // to login with any password
            $user->setPassword('no password');
        }
        // make sure the Facebook user is set
        $user->setFbid($facebookUser->getId());
        $em->persist($user);
        $em->flush();
        return $user;
        /*
        if(!$check){
            $hash = uniqid();
            //Encode the password (you could also do this via Doctrine listener)
            $password = $this->get('security.password_encoder')
                ->encodePassword($user, $hash);
            $user->setEmail($email);
            $user->setFirstname($facebookUser->getFirstName());
            $user->setLastname($facebookUser->getLastName());
            $user->setCity($facebookUser->getLocale());
            $user->setPassword($password);
            $user->setCreationtime();
            $user->setStatus(1);
            // 4) save the User!
            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();
            return $user;
        }
        return $user;
         * 
         */
    }
    public function checkCredentials($credentials, UserInterface $user)
    {
        return true;
    }
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        // this would happen if something went wrong in the OAuth flow
        $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
        $url = $this->container->get('router')
            ->generate('login_route');
        return new RedirectResponse($url);
    }
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        $key = '_security.main.target_path'; #where "main" is your firewall name
        //check if the referrer session key has been set 
        if ($this->container->get('session')->has($key)) {
            //set the url based on the link they were trying to access before being authenticated
            $url = $this->container->get('session')->get($key);
            //remove the session key
            $this->container->get('session')->remove($key);
        }
        //if the referrer key was never set, redirect to a default route
        else{
            $url = $this->container->get('router')->generate('home_page');
        }
        return new RedirectResponse($url); 
        // todo - remove needing this crazy thing
        /*
        $targetPath = $request->getSession()->get('_security.'.$providerKey.'.target_path');
        if ($targetPath) { 
            $router = $this->container->get('router');
            $targetPath = $router->generate('home_page');
        }
        return new RedirectResponse($targetPath);
         * 
         */
    }
    public function supportsRememberMe()
    {
        return true;
    }

    /**
     * Called when an anonymous user tries to access an protected page.
     *
     * In our app, this is never actually called, because there is only *one*
     * "entry_point" per firewall and in security.yml, we're using
     * app.form_login_authenticator as the entry point (so it's start() method
     * is the one that's called).
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        // not called in our app, but if it were, redirecting to the
        // login page makes sense
        $url = $this->container->get('router')
            ->generate('home_page');
        return new RedirectResponse($url);
    }
    protected function getDefaultSuccessRedirectUrl()
    {
        return $this->container->get('router')->generate('home_page');
    }
    protected function getLoginUrl()
    {
        return $this->container->get('router')->generate('login_route');
    }
    public function getDoctrine()
    {
        return $this->container->get('doctrine');
    }
    public function get($id)
    {
        return $this->container->get($id);
    }
}

Security.yml:

security:
    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
    encoders:
        Vendor'GiftBundle'Entity'Logins:
            algorithm: bcrypt
            cost: 13
        Symfony'Component'Security'Core'User'User:
            algorithm: bcrypt
            cost: 13
    #make admin inherit user access
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
#        ROLE_SUPER_ADMIN: ROLE_ADMIN
    #provider of user authetification name, in our case, email
    providers:
        doctrine1:
            entity: 
                class: Vendor'GiftBundle'Entity'Logins
                property: email
#        in_memory:
#            memory: ~

    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            #pattern: ^/.*
            #security: false
            anonymous: ~
            form_login:
                login_path: /login
                check_path: /login_check
                csrf_token_generator: security.csrf.token_manager
                username_parameter: _email
            provider: doctrine1
            logout:
                path: /logout
                target: /
            anonymous: ~
            guard:
                authenticators:
                    - app.facebook_authenticator
                #entry_point: app.form_login_authenticator
            #anonymous:    ~
            # activate different ways to authenticate
            # http_basic: ~
            # http://symfony.com/doc/current/book/security.html#a-configuring-how-your-users-will-authenticate
            # form_login: ~
            # http://symfony.com/doc/current/cookbook/security/form_login_setup.html
    access_control:
        - { path: ^/connect, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/reset, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/sec, role: ROLE_USER }
        - { path: ^/admin, role: ROLE_ADMIN }

手动设置令牌不是正确的解决方案-它是解决更深层次问题的一种方法。tl;dr是有一个问题序列化您的User对象,这导致Symfony认为User对象在登录后的第一个请求时发生了更改,然后它将您注销。

修复是(取决于你的应用程序)删除任何自定义serialize()方法你有在你的用户对象或删除AdvancedUserInterface从用户(和相应的方法&属性)。更详细的解释可以在这里找到:https://symfonycasts.com/blog/KnpUOAuth2ClientBundle#comment-4662612127

干杯!

我手工生成了token

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
    $token = new UsernamePasswordToken($token->getUser(), null, $providerKey, $token->getUser()->getRoles());
    $this->tokenStorage->setToken($token);
    $request->getSession()->set('_security_main', serialize($token));
    $url = $this->router->generate("homepage");
    return new RedirectResponse($url);
}

试试这个。别忘了注入TokenStorage