一個登陸框實現不一樣的登陸驗證

一.本地user登陸

1. 定義一個User

<?php

namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @MongoDB\Document(collection="user")
 */
class User implements UserInterface, \Serializable
{
    /**
     * @MongoDB\Id(strategy="auto")
     */
    private $id;

    /**
     * @MongoDB\Field(type="string")
     */
    private $username;

    /**
     * @MongoDB\Field(type="string")
     */
    private $password;

    /**
     * @MongoDB\Field(type="string")
     */
    private $email;

    /**
     * @var boolean
     * @MongoDB\Field(type="boolean")
     */
    private $isActive;
    ....

2. 自定義user provide

<?php

namespace AppBundle\Provide;

use Doctrine\ODM\MongoDB\DocumentManager;
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 UserProvide implements UserProviderInterface
{
    /**
     * @var DocumentManager
     */
    protected $dm;

    public function __construct(DocumentManager $dm)
    {
        $this->dm = $dm;
    }

    /**
     * Loads the user for the given username.
     *
     * This method must throw UsernameNotFoundException if the user is not
     * found.
     *
     * @param string $username The username
     *
     * @return UserInterface
     *
     * @throws UsernameNotFoundException if the user is not found
     */
    public function loadUserByUsername($username)
    {
        $user = $this->dm->getRepository("AppBundle:User")->findOneBy(array('username'=>$username));

        if ($user) {
           return $user;
        }

        throw new UsernameNotFoundException(
            sprintf('Username "%s" does not exist.', $username)
        );
    }

    /**
     * Refreshes the user for the account interface.
     *
     * It is up to the implementation to decide if the user data should be
     * totally reloaded (e.g. from the database), or if the UserInterface
     * object can just be merged into some internal array of users / identity
     * map.
     *
     * @param UserInterface $user
     *
     * @return UserInterface
     *
     * @throws UnsupportedUserException if the account is not supported
     */
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof UserInterface) {
            throw new UnsupportedUserException(
                sprintf('Instances of "%s" are not supported.', get_class($user))
            );
        }

        return $this->loadUserByUsername($user->getUsername());
    }

    /**
     * Whether this provider supports the given user class.
     *
     * @param string $class
     *
     * @return bool
     */
    public function supportsClass($class)
    {
        return $class === 'AppBundle\Document\User';
    }
}
// services.yml
    app.user.provide:
      class: AppBundle\Provide\UserProvide
      arguments:
          - "@doctrine.odm.mongodb.document_manager"

二. 其餘方式認證(這裏用另外一個user表驗證)

###1.TestUserphp

namespace AppBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @MongoDB\Document(collection="test_user")
 */
class TestUser implements UserInterface, \Serializable
{
    /**
     * @MongoDB\Id(strategy="auto")
     */
    private $id;

    /**
     * @MongoDB\Field(type="string")
     */
    private $username;

    /**
     * @MongoDB\Field(type="string")
     */
    private $password;

    /**
     * @MongoDB\Field(type="string")
     */
    private $email;
    ...

###2. TestUserProvidecss

<?php

namespace AppBundle\Provide;

use Doctrine\ODM\MongoDB\DocumentManager;
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 TestUserProvide implements UserProviderInterface
{
    /**
     * @var DocumentManager
     */
    protected $dm;

    public function __construct(DocumentManager $dm)
    {
        $this->dm = $dm;
    }

    /**
     * Loads the user for the given username.
     *
     * This method must throw UsernameNotFoundException if the user is not
     * found.
     *
     * @param string $username The username
     *
     * @return UserInterface
     *
     * @throws UsernameNotFoundException if the user is not found
     */
    public function loadUserByUsername($username)
    {
        $user = $this->dm->getRepository("AppBundle:TestUser")->findOneBy(array('username'=>$username));

        if ($user) {
           return $user;
        }

        throw new UsernameNotFoundException(
            sprintf('Username "%s" does not exist.', $username)
        );
    }

    /**
     * Refreshes the user for the account interface.
     *
     * It is up to the implementation to decide if the user data should be
     * totally reloaded (e.g. from the database), or if the UserInterface
     * object can just be merged into some internal array of users / identity
     * map.
     *
     * @param UserInterface $user
     *
     * @return UserInterface
     *
     * @throws UnsupportedUserException if the account is not supported
     */
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof UserInterface) {
            throw new UnsupportedUserException(
                sprintf('Instances of "%s" are not supported.', get_class($user))
            );
        }

        return $this->loadUserByUsername($user->getUsername());
    }

    /**
     * Whether this provider supports the given user class.
     *
     * @param string $class
     *
     * @return bool
     */
    public function supportsClass($class)
    {
        return $class === 'AppBundle\Document\TestUser';
    }
}
//services.yml
    app.test_user.provide:
      class: AppBundle\Provide\TestUserProvide
      arguments:
          - "@doctrine.odm.mongodb.document_manager"

3.建立自定義Authentication Token

<?php

namespace AppBundle\Security\Authentication\Token;

use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;

class TestToken extends AbstractToken
{

    /**
     * @param array $roles
     */
    public function __construct(array $roles = array())
    {
        parent::__construct($roles);

        $this->setAuthenticated(count($roles) > 0);
    }

    /**
     * Returns the user credentials.
     *
     * @return mixed The user credentials
     */
    public function getCredentials()
    {
        return '';
    }
}

###4.建立自定義Authentication Providemongodb

<?php

namespace AppBundle\Security\Authentication\Provide;

use AppBundle\Security\Authentication\Token\TestToken;
use AppBundle\Security\TestUserAuthenticate;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class TestProvide implements AuthenticationProviderInterface
{

    protected  $userProvider;
    protected  $testUserAuthenticate;

    /**
     * @param UserProviderInterface $userProvider
     */
    public function __construct(UserProviderInterface $userProvider,TestUserAuthenticate $testUserAuthenticate)
    {
        $this->userProvider = $userProvider;
        $this->testUserAuthenticate =  $testUserAuthenticate;
    }

    /**
     * Attempts to authenticate a TokenInterface object.
     *
     * @param TokenInterface $token The TokenInterface instance to authenticate
     *
     * @return TokenInterface An authenticated TokenInterface instance, never null
     *
     * @throws AuthenticationException if the authentication fails
     */
    public function authenticate(TokenInterface $token)
    {
        $user = $this->userProvider->loadUserByUsername($token->getUsername());
        if ($user && $this->testUserAuthenticate->isAuthenticationValid($user,$token->getUsername(),$token->getCredentials())) {
            $authenticatedToken = new TestToken($user->getRoles());
            $authenticatedToken->setUser($user);

            return $authenticatedToken;
        }

        throw new AuthenticationException('The test authentication failed.');
    }

    /**
     * Checks whether this provider supports the given token.
     *
     * @param TokenInterface $token A TokenInterface instance
     *
     * @return bool true if the implementation supports the Token, false otherwise
     */
    public function supports(TokenInterface $token)
    {
        return $token instanceof TestToken || $token instanceof UsernamePasswordToken ;
    }
}

###5.驗證輸入的密碼是否正確(注意上面第4步isAuthenticationValid方法)數據庫

<?php

namespace AppBundle\Security;

use Doctrine\ODM\MongoDB\DocumentManager;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;

class TestUserAuthenticate
{

    /**
     * @var EncoderFactoryInterface
     */
    protected $encoderFactory;

    /**
     * @var DocumentManager
     */
    protected $dm;

    public function __construct(DocumentManager $dm,EncoderFactoryInterface $encoderFactory)
    {
        $this->dm = $dm;
        $this->encoderFactory = $encoderFactory;
    }

    public function isAuthenticationValid($user,$username,$password)
    {
        $password = $this->encoderFactory->getEncoder($user)->encodePassword($password,$user->getSalt());
        $result = $this->dm->getRepository('AppBundle:TestUser')->findOneBy(array('username'=>$username,'password'=>$password));
        if(empty($result)){
            return false;
        }else{
            return true;
        }
    }
}

6.建立本身的factory

<?php

namespace AppBundle\DependencyInjection\Security\Factory;

use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;

class TestFactory extends  FormLoginFactory
{
    /**
     * @param ContainerBuilder $container
     * @param string $id
     * @param array $config
     * @param string $userProviderId
     * @return string
     */
    protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
    {
        $providerId = 'security.authentication.provider.test.' . $id;

        $container->setDefinition($providerId, new DefinitionDecorator('app.test.security.authentication.provider'))
            ->replaceArgument(0, new Reference($userProviderId));

        return $providerId;
    }

    /**
     * @return string
     */
    public function getPosition()
    {
        return 'form';
    }

    /**
     * @return string
     */
    public function getKey()
    {
        return 'test-login';
    }
}

7.在security context增長自定義的factory

<?php

namespace AppBundle;

use AppBundle\DependencyInjection\Security\Factory\TestFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class AppBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $extension = $container->getExtension('security');
        $extension->addSecurityListenerFactory(new TestFactory());
    }
}

8.完整的services.yml

services:
  app.user.provide:
      class: AppBundle\Provide\UserProvide
      arguments:
          - "@doctrine.odm.mongodb.document_manager"

  app.test.authentivate:
      class: AppBundle\Security\TestUserAuthenticate
      arguments:
          - "@doctrine.odm.mongodb.document_manager"
          - "@security.encoder_factory"

  app.test_user.provide:
      class: AppBundle\Provide\TestUserProvide
      arguments:
          - "@doctrine.odm.mongodb.document_manager"

  app.test.security.authentication.provider:
      class: AppBundle\Security\Authentication\Provide\TestProvide
      arguments:
       - ''
       - '@app.test.authentivate'

9.完整的security.yml

security:
    encoders:
        AppBundle\Document\User: plaintext
        AppBundle\Document\TestUser: plaintext
    providers:
        chain_provider:
            chain:
              providers: [user_provider,test_user_provider]
        user_provider:
          id: app.user.provide
        test_user_provider:
          id: app.test_user.provide

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        main:
            test_login:
                provider: test_user_provider
                login_path: login
                check_path: login
                default_target_path: homepage
            form_login:
                provider: user_provider
                login_path: login
                check_path: login
                default_target_path: homepage
            logout:
                path:   logout
                target: login
            anonymous: ~

三.小結

按照上面的步驟就能實如今一個輸入框經過兩種不一樣的方式登陸。若是在項目運行的過程當中,只須要本地的User,這個實現也很簡單,只須要在自定義的第三方的UserProvide裏面返回本地User就能夠了,思路以下:app

// TestUserProvide
public function loadUserByUsername($username)
    {
        //$user = $this->dm->getRepository("AppBundle:TestUser")->findOneBy(array('username'=>$username));

        //if ($user) {
         //  return $user;
        //}
        //1.驗證輸入的用戶名在第三方系統中正確,並返回用戶信息(TestUser)
        //2.查看本地數據庫是否存在該用戶名的用戶,若是有直接返回User,沒有,根據TestUser的信息新建一個User,存儲到本地,返回新建立的User
        //3.此時雖然不一樣用戶登陸時使用的Token不同,可是$token->getUser()取的都是本地User對象的實例

        throw new UsernameNotFoundException(
            sprintf('Username "%s" does not exist.', $username)
        );
    }
相關文章
相關標籤/搜索