namespace MyBundle\Models; use Mh\Mongo\Model\Base; use Symfony\Component\Security\Core\User\AdvancedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; class User extends Base implements UserInterface { public function getRoles() { return $this->credentials; } public function getPassword() { return $this->passw; } public function getSalt() { return $this->salt; } public function getUsername() { return $this->uname; } public function eraseCredentials() { } public function equals(UserInterface $user) { return $this->getUsername() === $user->getUsername(); } public function __toString() { return $this->uname; } }
namespace MyBundle\Security; use Mh\Mongo\MongoBundle\ConnectionManager; use MyBundle\Models\User; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use \Exception; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; class UserProvider implements UserProviderInterface { protected $collection; protected $db; protected $field; protected $logger; // , , // , // . public function __construct(array $params, ConnectionManager $cm, LoggerInterface $logger) { // $this->db = $cm->getConnection($params['confname']); $this->collection = $this->db->selectCollection($params['collection']); // , $this->field = $params['field']; if (!$this->field || !$this->collection) { throw new Exception("Invalid parameters"); } // logger', DI $this->logger = $logger; } // , // . public function loadUserByUsername($uname) { // . $this->logger->debug("user load request. name: $uname"); // User // . $user = $this->collection->findOne(array($this->field => $uname)); // . , . if (!isset($user->uname) || $user->uname !== $uname) { throw new UsernameNotFoundException(sprintf('User "%s" does not exist.', $uname)); } // return $user; } // . // . public function refreshUser(UserInterface $user) { if (!($user instanceof User)) throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); $this->logger->info("refresh from mongo"); $_user = $this->collection->findOne(array('_id' => $user->_id)); if ($_user && $_user instanceof User) $this->logger->info("roles: " .implode(', ',$_user->roles)); else throw new UsernameNotFoundException(sprintf('User "%s" does not exist.', $user->uname)); return $_user; } // . public function supportsClass($class) { $this->logger->debug("support checking: $class"); if ($class == 'MyBundle\Models\User') return true; return false; } }
parameters: my.users: confname: default collection: user field: uname services: my.users.prov: class: MyBundle \ Security \ UserProvider arguments: [% my.users%, @ mongo.manager, @ monolog.logger]
security: encoders: Symfony \ Component \ Security \ Core \ User \ User: plaintext # for ease of launch, you can store an unencrypted password in the database MyBundle \ Models \ User: plaintext providers: # now declare a new user source (you can assign any name) # and write in it the name of the service that will give the data about users mongobase: id: my.userprov ...
namespace MyBundle\Social\Authentication; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; class Token extends AbstractToken { // . public $social; public $hash; public $add; // , function __construct(array $roles = array()) { parent::__construct($roles); // , // . parent::setAuthenticated(count($roles) > 0); } // , TokenInterface public function getCredentials() { } // , // . “” // . public function serialize() { $pser = parent::serialize(); return serialize(array($this->social, $this->hash, $this->add, $pser)); } public function unserialize($serialized) { list($this->social, $this->hash, $this->add, $pser) = unserialize($serialized); parent::unserialize($pser); } }
namespace MyBundle\Social\Authentication; // AbstractAuthenticationListener, // “” , // use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Http\Firewall\ListenerInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; class AuthenticationListener extends AbstractAuthenticationListener { // “” . protected $social; // , // public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, array $options = array(), AuthenticationSuccessHandlerInterface $successHandler = null, AuthenticationFailureHandlerInterface $failureHandler = null, LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, array $social = array()) { parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, array_merge(array( 'intention' => 'authenticate', ), $options), $successHandler, $failureHandler, $logger, $dispatcher); $this->social = $social; } // attemptAuthentication , // , Token' public function attemptAuthentication(Request $request) { // , // if ($request->get('uid') && $request->get('hash') && $request->cookies->get("vk_app_{$this->social['vk']['id']}")) { $this->logger->debug("vk auth handled"); // $uid = $request->get('uid'); $fn = $request->get('first_name'); $ln = $request->get('last_name'); $hash = $request->get('hash'); $this->logger->info("user $fn $ln [$uid] // $hash"); $avatars = array( 'sav' => $request->get('photo'), 'srav' => $request->get('photo_rec'), ); // token // . … $token = new Token(); $token->setUser("vk{$uid}"); // … token' ... $token->social = 'vk'; $token->hash = $hash; $token->add = array( 'uid' => $uid, 'avatar' => $avatars, 'name' => "$fn $ln", ); // … , return $this->authenticationManager->authenticate($token); } // - . } }
namespace MyBundle\Social\Authentication; use Mh\Mongo\MongoBundle\ConnectionManager; use MyBundle\Models\User as User; use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\NonceExpiredException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\HttpKernel\Log\LoggerInterface; // AuthenticationProvider' // , UserAuthenticationProvider, // . class Provider implements AuthenticationProviderInterface { protected $userProvider; protected $logger; // . protected $social; // MongoDB // protected $mongo; // , // . public function __construct(UserProviderInterface $userProvider, array $social, ConnectionManager $cm, LoggerInterface $logger) { $this->userProvider = $userProvider; $this->social = $social; $this->mongo = $cm; $this->logger = $logger; } // public function authenticate(TokenInterface $token) { $user = null; // UserProvider'a // , try { $user = $this->userProvider->loadUserByUsername($token->getUsername()); } catch (UsernameNotFoundException $ex) { $this->logger->debug("user ".$token->getUsername()." not yet registred"); } try { // hash' if ($this->checkHash($token)) { $this->logger->info("hash is valid"); // , hash, // - “” if (!$user) { $this->logger->info("register new user"); $user = new User(array( 'uname' => $token->getUsername(), 'social' => $token->social, 'fullname' => $token->add['name'], 'avatar' => $token->add['avatar'], 'suid' => $token->add['uid'], 'roles' => array('ROLE_EXTUSER', strtoupper($token->social) ), )); $user->save($this->mongo); } // . , Token // $authenticatedToken = new Token($user->getRoles()); $authenticatedToken->social = $token->social; $authenticatedToken->hash = $token->hash; $authenticatedToken->add = $token->add; $authenticatedToken->setUser($user); // return $authenticatedToken; } else { $this->logger->debug("hash is invalid."); } } catch (\Exception $ex) { $this->logger->err("auth internal exception: $ex"); } // - - // . throw new AuthenticationException('The Social authentication failed.'); } // hesh', // . protected function checkHash(Token $token) { if ($token->social == 'vk') { return ($token->hash === md5( $this->social['vk']['id'] . $token->add['uid'] . $this->social['vk']['key'] )); } return false; } // , token' provider' public function supports(TokenInterface $token) { return $token instanceof Token; } }
namespace MyBundle\DependencyInjection\Security; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\Config\Definition\Builder\NodeDefinition; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface; // Symfony AbstractFactory . // , , // AuthenticationProvider' class SocialAuthFactory implements SecurityFactoryInterface { // firewall' // . form_login. protected $options = array( 'check_path' => '/login_check', 'login_path' => '/login', 'use_forward' => false, 'always_use_default_target_path' => false, 'default_target_path' => '/', 'target_path_parameter' => '_target_path', 'use_referer' => false, 'failure_path' => null, 'failure_forward' => false, ); // , firewall listener // provider. // , id firewall' // , . public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint) { // , // . // “” // ( ) . // id AuthenticationProvider' $providerId = 'security.authentication.provider.social.'.$id; // - my.socialauth.prov // (0) // $container ->setDefinition($providerId, new DefinitionDecorator('my.socialauth.prov')) ->replaceArgument(0, new Reference($userProvider)) ; // listener' , provider' $listenerId = 'security.authentication.listener.social.'.$id; $container ->setDefinition($listenerId, new DefinitionDecorator('my.socialauth.listener')) ->replaceArgument(4, $id) ->replaceArgument(5, array_intersect_key($config, $this->options)); // id . return array($providerId, $listenerId, $defaultEntryPoint); } // , // . login_form. public function getPosition() { return 'form'; } // , // . public function getKey() { return 'mysocial'; } // , // AbstractFactory public function addConfiguration(NodeDefinition $node) { $builder = $node->children(); $builder ->scalarNode('provider')->end() ; foreach ($this->options as $name => $default) { if (is_bool($default)) { $builder->booleanNode($name)->defaultValue($default); } else { $builder->scalarNode($name)->defaultValue($default); } } } }
security: factories: - "% kernel.root_dir% / .. / src / MyBundle / Resources / config / socialauth_factory.yml" ...
services: security.authentication.factory.mysocial: class: MyBundle \ DependencyInjection \ Security \ Factory \ SocialFactory tags: - {name: security.listener.factory}
parameters: my.users: confname: default collection: user field: uname my.social: id: __YOUR_APP_ID__ key: __YOUR_APP_PRIVATE_KEY__ services: my.users.prov: class: MyBundle \ Security \ UserProvider arguments: [% my.users%, @ mongo.manager, @ monolog.logger] # service for the listener we declare as the heir to the AbstractListener service # the arguments specified here will be passed to the constructor after the arguments # prescribed in the parent service my.socialauth.listener: class: MyBundle \ Social \ Authentication \ Listener parent: security.authentication.listener.abstract arguments: [% my.social%] # in the service for provider, we leave the first argument without real value. # The value will be transferred from the decorator created in Factory. my.socialauth.prov: class: MyBundle \ Social \ Authentication \ Provider arguments: ['',% my.social%, @ mongo.manager, @ monolog.logger]
... firewalls: myauth: pattern: ^ / # add our firewall to the block to the general form_login, # Recall that he has almost the same settings. mysocial: check_path: / login / socialauth login_path: / login / in # our classes will work on equal terms with boxed classes and symfony, # so that they do not interfere with each other, we will do different check_path form_login: check_path: / login / auth login_path: / login / in # we allow to log in 2 ways, the output is for # Both types to the user by standard means. logout: path: / login / out target: / invalidate_session: true anonymous: ~ ...
Source: https://habr.com/ru/post/134005/
All Articles