一.本地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.TestUser
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. TestUserProvide
<?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 Provide
<?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就可以了,思路如下:
// 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)
);
}