首页 文章

使用Symfony2通过API对用户密码进行身份验证

提问于
浏览
2

我有一个OAuth API,需要用户名和密码才能获取用户对象(资源所有者密码凭据流) . 我想要得到这个最终结果:

  • 用户输入用户名/密码

  • Symfony交换用户名/密码以访问和刷新令牌,然后获取User对象并使用获取的对象填充令牌

  • 用户现已在网站上进行身份验证

我遇到的问题是我似乎无法弄清楚如何以我能看到的最佳方式:用户提供商 . UserProviderInterface要求实现loadUserByUsername(),但我不能这样做,因为我需要用户名和密码来获取用户对象 .

我试图实现SimplePreAuthenticatorInterface,但我仍遇到同样的问题:在 createToken() 中创建PreAuthenticated令牌后,我需要使用 authenticateToken() 对其进行身份验证,我仍然无法通过UserProvider获取用户,因为我首先必须使用用户名/密码获取一个访问令牌'd allow me to fetch the User object. I thought about adding a method to login in my UserProvider that'使用用户名/密码通过API登录并存储数组中任何用户名的登录令牌,然后通过该阵列中的用户名获取令牌,但这感觉不对 .

我是从错误的角度看它吗?我根本不应该使用PreAuthenticated令牌吗?

1 回答

  • 5

    前段时间我需要实现一种通过Web服务对用户进行身份验证的方法 . 这就是我最终基于这个doc以及symfony核心的表单登录实现 .

    首先创建一个表示请求中存在的用户身份验证数据的令牌:

    use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
    
    class WebserviceAuthToken extends AbstractToken
    {
        /**
         * The password of the user.
         *
         * @var string
         */
        private $password;
    
        /**
         * Authenticated Session ID.
         *
         * @var string
         */
        private $authSessionID;
    
        public function __construct($user, $password, array $roles = array())
        {
            parent::__construct($roles);
    
            $this->setUser($user);
            $this->password = $password;
    
            parent::setAuthenticated(count($roles) > 0);
    
        }
    
        /**
         * {@inheritDoc}
         */
        public function getCredentials()
        {
            return '';
        }
    
        /**
         * Returns the Authenticated Session ID.
         *
         * @return string
         */
        public function getAuthSessionID()
        {
            return $this->authSessionID;
        }
    
        /**
         * Sets the Authenticated Session ID.
         *
         * @param string $authSessionID
         */
        public function setAuthSessionID($authSessionID)
        {
            $this->authSessionID = $authSessionID;
        }
    
        /**
         * Returns the Password used to attempt login.
         *
         * @return string
         */
        public function getPassword()
        {
            return $this->password;
        }
    
        /**
         * {@inheritDoc}
         */
        public function serialize()
        {
            return serialize(array(
                $this->authSessionID,
                parent::serialize()
            ));
        }
    
        /**
         * {@inheritDoc}
         */
        public function unserialize($serialized)
        {
            $data = unserialize($serialized);
                list(
                    $this->authSessionID,
                    $parent,
                ) = $data;
    
            parent::unserialize($parent);
        }
    
    }
    

    我存储的AuthSessionID是从Web服务返回的令牌,允许我作为经过身份验证的用户执行请求 .

    创建一个Webservice身份验证侦听器,负责向防火墙发送请求并调用身份验证提供程序:

    use RPanelBundle\Security\Authentication\Token\RPanelAuthToken;
    use Psr\Log\LoggerInterface;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
    use Symfony\Component\Security\Core\Security;
    use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
    use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
    use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
    use Symfony\Component\Security\Http\HttpUtils;
    use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
    use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
    use Symfony\Component\EventDispatcher\EventDispatcherInterface;
    
    class WebserviceAuthListener extends AbstractAuthenticationListener
    {
        private $csrfTokenManager;
    
        /**
         * {@inheritdoc}
         */
        public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $csrfTokenManager = null)
        {
            if ($csrfTokenManager instanceof CsrfProviderInterface) {
                $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager);
            } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) {
                throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.');
            }
    
            parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
                'username_parameter' => '_username',
                'password_parameter' => '_password',
                'csrf_parameter' => '_csrf_token',
                'intention' => 'authenticate',
                'post_only' => true,
            ), $options), $logger, $dispatcher);
    
            $this->csrfTokenManager = $csrfTokenManager;
        }
    
        /**
         * {@inheritdoc}
         */
        protected function requiresAuthentication(Request $request)
        {
            if ($this->options['post_only'] && !$request->isMethod('POST')) {
                return false;
            }
    
            return parent::requiresAuthentication($request);
        }
    
        /**
         * {@inheritdoc}
         */
        protected function attemptAuthentication(Request $request)
        {
            if (null !== $this->csrfTokenManager) {
                $csrfToken = $request->get($this->options['csrf_parameter'], null, true);
    
                if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) {
                    throw new InvalidCsrfTokenException('Invalid CSRF token.');
                }
            }
    
            if ($this->options['post_only']) {
                $username = trim($request->request->get($this->options['username_parameter'], null, true));
                $password = $request->request->get($this->options['password_parameter'], null, true);
            } else {
                $username = trim($request->get($this->options['username_parameter'], null, true));
                $password = $request->get($this->options['password_parameter'], null, true);
            }
    
            $request->getSession()->set(Security::LAST_USERNAME, $username);
    
            return $this->authenticationManager->authenticate(new WebserviceAuthToken($username, $password));
        }
    
    }
    

    创建一个Webservice登录工厂,我们进入安全组件,并告诉用户提供程序和可用选项:

    class WebserviceFormLoginFactory extends FormLoginFactory
    {
        /**
         * {@inheritDoc}
         */
        public function getKey()
        {
            return 'webservice-form-login';
        }
    
        /**
         * {@inheritDoc}
         */
        protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
        {
            $provider = 'app.security.authentication.provider.'.$id;
    
            $container
                ->setDefinition($provider, new DefinitionDecorator('app.security.authentication.provider'))
                ->replaceArgument(1, new Reference($userProviderId))
                ->replaceArgument(2, $id);
    
            return $provider;
        }
    
        /**
         * {@inheritDoc}
         */
        protected function getListenerId()
        {
            return 'app.security.authentication.listener';
        }
    
    }
    

    创建一个身份验证提供程序,以验证WebserviceAuthToken的有效性

    class WebserviceAuthProvider implements AuthenticationProviderInterface
    {
        /**
         * Service to handle DMApi account related calls.
         *
         * @var AccountRequest
         */
        private $apiAccountRequest;
    
        /**
         * User provider service.
         *
         * @var UserProviderInterface
         */
        private $userProvider;
    
        /**
         * Security provider key.
         *
         * @var string
         */
        private $providerKey;
    
        public function __construct(AccountRequest $apiAccountRequest, UserProviderInterface $userProvider, $providerKey)
        {
            $this->apiAccountRequest = $apiAccountRequest;
            $this->userProvider = $userProvider;
            $this->providerKey = $providerKey;
        }
    
        /**
         * {@inheritdoc}
         */
        public function authenticate(TokenInterface $token)
        {
            // Check if both username and password exist
            if (!$username = $token->getUsername()) {
                throw new AuthenticationException('Username is required to authenticate.');
            }
    
            if (!$password = $token->getPassword()) {
                throw new AuthenticationException('Password is required to authenticate.');
            }
    
            // Authenticate the User against the webservice
            $loginResult = $this->apiAccountRequest->login($username, $password);
    
            if (!$loginResult) {
                throw new BadCredentialsException();
            }
    
            try {
    
                $user = $this->userProvider->loadUserByWebserviceResponse($loginResult);
    
                // We dont need to store the user password
                $authenticatedToken = new WebserviceAuthToken($user->getUsername(), "", $user->getRoles());
                $authenticatedToken->setUser($user);
                $authenticatedToken->setAuthSessionID($loginResult->getAuthSid());
                $authenticatedToken->setAuthenticated(true);
    
                return $authenticatedToken;
    
            } catch (\Exception $e) {
                throw $e;
            }
        }
    
        /**
         * {@inheritdoc}
         */
        public function supports(TokenInterface $token)
        {
            return $token instanceof WebserviceAuthToken;
        }
    
    }
    

    最后创建一个用户提供商 . 在我收到来自webservice的响应后,我检查用户是否存储在redis上,如果没有,我创建它 . 之后,用户总是从redis加载 .

    class WebserviceUserProvider implements UserProviderInterface
    {
    
        /**
         * Wrapper to Access the Redis.
         *
         * @var RedisDao
         */
        private $redisDao;
    
        public function __construct(RedisDao $redisDao)
        {
            $this->redisDao = $redisDao;
        }
    
        /**
         * {@inheritdoc}
         */
        public function loadUserByUsername($username)
        {
            // Get the UserId based on the username
            $userId = $this->redisDao->getUserIdByUsername($username);
    
            if (!$userId) {
                throw new UsernameNotFoundException("Unable to find an UserId identified by Username = $username");
            }
    
            if (!$user = $this->redisDao->getUser($userId)) {
                throw new UsernameNotFoundException("Unable to find an User identified by ID = $userId");
            }
    
            if (!$user instanceof User) {
                throw new UnsupportedUserException();
            }
    
            return $user;
        }
    
        /**
         * Loads an User based on the webservice response.
         *
         * @param  \AppBundle\Service\Api\Account\LoginResult $loginResult
         * @return User
         */
        public function loadUserByWebserviceResponse(LoginResult $loginResult)
        {
            $userId = $loginResult->getUserId();
            $username = $loginResult->getUsername();
    
            // Checks if this user already exists, otherwise we need to create it
            if (!$user = $this->redisDao->getUser($userId)) {
    
                $user = new User($userId, $username);
    
                if (!$this->redisDao->setUser($user) || !$this->redisDao->mapUsernameToId($username, $userId)) {
                    throw new \Exception("Couldnt create a new User for username = $username");
                }
    
            }
    
            if (!$user instanceof User) {
                throw new UsernameNotFoundException();
            }
    
            if (!$this->redisDao->setUser($user)) {
                throw new \Exception("Couldnt Update Data for for username = $username");
            }
    
            return $this->loadUserByUsername($username);
        }
    
        /**
         * {@inheritdoc}
         */
        public function refreshUser(UserInterface $user)
        {
            if (!$user instanceof User) {
                throw new UnsupportedUserException(
                    sprintf('Instances of "%s" are not supported.', get_class($user))
                );
            }
    
            return $this->loadUserByUsername($user->getUsername());
        }
    
        /**
         * {@inheritdoc}
         */
        public function supportsClass($class)
        {
            return $class === 'AppBundle\Entities\User';
        }
    }
    

    所需服务:

    app.security.user.provider:
            class: AppBundle\Security\User\WebserviceUserProvider
            arguments: ["@app.dao.redis"]
    
        app.security.authentication.provider:
            class: AppBundle\Security\Authentication\Provider\WebserviceAuthProvider
            arguments: ["@api_caller", "", ""]
    
        app.security.authentication.listener:
            class: AppBundle\Security\Firewall\WebserviceAuthListener
            abstract:  true
            parent: security.authentication.listener.abstract
    

    配置安全性:

    security:
        providers:
            app_user_provider:
                id: app.security.user.provider
    
        firewalls:
            default:
                pattern: ^/
                anonymous: ~
                provider: app_user_provider
                webservice_form_login: # Configure just like form_login from the Symfony core
    

    如果您有任何疑问,请告诉我 .

相关问题