markdown Zend Framework 3:身份验证

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown Zend Framework 3:身份验证相关的知识,希望对你有一定的参考价值。

# Authentication Service
We use Authentication Service of Zend for implement the authentication feature.
First create your customer Adapter for Authentication Service that name `AuthAdapter`.
## AuthAdapter
```php
namespace User\Service;

use Doctrine\ORM\EntityManager;
use User\Entity\User;
use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Result;
use Zend\Crypt\Password\Bcrypt;

class AuthAdapter implements AdapterInterface {

    /**
     * User email
     * @var string
     */
    private $email;

    /**
     * Password
     * @var string
     */
    private $password;

    /**
     * Entity Manager.
     * @var EntityManager
     */
    private $entityManager;

    /**
     * AuthAdapter constructor.
     * @param $entityManager
     */
    public function __construct($entityManager) {
        $this->entityManager = $entityManager;
    }

    /**
     * Set user email
     * @param $email
     */
    public function setEmail($email) {
        $this->email = $email;
    }

    /**
     * Set password
     * @param $password
     */
    public function setPassword($password) {
        $this->password = $password;
    }

    public function authenticate()
    {
        // Check the database if there is a user with such email.
        $user = $this->entityManager->getRepository(User::class)->findOneByEmail($this->email);

        // If there is no such user, return 'Identity Not Found' status.
        if ($user == null)
            return new Result(
                Result::FAILURE_IDENTITY_NOT_FOUND,
                null,
                ['Invalid credentials.']
            );

        // If the user with such email exists, we need to check if it is active or retired.
        // Do not allow retired users to log in.
        if ($user->getStatus() == User::STATUS_RETIRED)
            return new Result(
                Result::FAILURE,
                null,
                ['User is retired.']
            );

        // Now we need to calculate hash based on user-entered password and compare
        // it with the password hash stored in database.
        $bcrypt = new Bcrypt();
        $passwordHash = $user->getPassword();

        if ($bcrypt->verify($this->password, $passwordHash))
            // Great! The password hash matches. Return user identity (email) to be
            // saved in session for later use.
            return new Result(
                Result::SUCCESS,
                $this->email,
                ['Authenticated successfully.']
            );

        // If password check didn't pass return 'Invalid Credential' failure status.
        return new Result(
            Result::FAILURE_CREDENTIAL_INVALID,
            null,
            ['Invalid credentials.']
        );
    }
}
```
## AuthAdapterFactory
You have to factory `AuthAdapter` too:
```php
namespace User\Service\Factory;

use Interop\Container\ContainerInterface;
use User\Service\AuthAdapter;
use Zend\ServiceManager\Factory\FactoryInterface;

/**
 * This is the factory class for AuthAdapter service. The purpose of the factory
 * is to instantiate the service and pass it dependencies (inject dependencies)
 * @package User\Service\Factory
 */
class AuthAdapterFactory implements FactoryInterface {
    /**
     * This method creates the AuthAdapter service and returns its instance.
     * @param ContainerInterface $container
     * @param string $requestedName
     * @param array|null $options
     * @return object
     */
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        // Get Doctrine entity manager from Service Manager.
        $entityManager = $container->get('doctrine.entitymanager.orm_default');

        // Create the AuthAdapter and inject dependency to its constructor.
        return new AuthAdapter($entityManager);
    }
}
```
## AuthenticationServiceFactory
You have to inject your `AuthAdapter` to `AuthenticationService`. `AuthenticationServiceFactory` file contains:
```php
namespace User\Service\Factory;

use Interop\Container\ContainerInterface;
use User\Service\AuthAdapter;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\Session\SessionManager;

/**
 * The factory responsible for creating of authentication service.
 * Class AuthenticationServiceFactory
 * @package User\Service\Factory
 */
class AuthenticationServiceFactory implements FactoryInterface {

    /**
     * This method creates the Zend\Authentication\AuthenticationService service
     * and return its instance.
     *
     * @param ContainerInterface $container
     * @param string $requestedName
     * @param array|null $options
     * @return object|AuthenticationService
     */
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $sessionManager = $container->get(SessionManager::class);
        $authStorage = new Session('Zend_Auth', 'session', $sessionManager);
        $authAdapter = $container->get(AuthAdapter::class);

        // Create the service and inject dependencies into its constructor.
        return new AuthenticationService($authStorage, $authAdapter);
    }
}
```
## AuthManager
This file is used to handle login and logout of authentication feature.
```php
namespace User\Service;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Result;
use Zend\Session\SessionManager;

/**
 * Class AuthManager
 * @package User\Service
 */
class AuthManager {

    // Constants returned by the access filter.
    const ACCESS_GRANTED = 1; // Access to the page is granted.
    const AUTH_REQUIRED = 2; // Authentication is required to see the page.
    const ACCESS_DENIED = 3; // Access to the page is denied.

    /**
     * @var SessionManager
     */
    protected $sessionManager;

    /**
     * @var AuthenticationService
     */
    protected $authService;

    /**
     * Contents of the 'access_filter' config key.
     * @var array
     */
    protected $config;

    /**
     * AuthManager constructor.
     * @param SessionManager $sessionManager
     * @param AuthenticationService $authService
     * @param array $config
     */
    public function __construct(SessionManager $sessionManager, AuthenticationService $authService, $config)
    {
        $this->sessionManager = $sessionManager;
        $this->authService = $authService;
        $this->config = $config;
    }

    /**
     * Performs a login attempt. If $rememberMe argument is true, if forces the session
     * to last for one month (otherwise the session expires on one hour).
     *
     * @param $email
     * @param $password
     * @param $rememberMe
     * @return Result
     * @throws \Exception
     */
    public function login($email, $password, $rememberMe) {
        if ($this->authService->getIdentity() != null)
            throw new \Exception('Already logged in');

        $authAdapter = $this->authService->getAdapter();
        $authAdapter->setEmail($email);
        $authAdapter->setPassword($password);
        $result = $this->authService->authenticate();

        // If user wants to "remember him", we will make session to expire in
        // one month. By default session expires in 1 hour (as specific in our
        // config/global.php file).
        if ($result->getCode() == Result::SUCCESS && $rememberMe)
            // Session cookie will expire in 1 month (30 days).
            $this->sessionManager->rememberMe(60*60*24*30);

        return $result;
    }

    /**
     * Performs user logout.
     * @throws \Exception
     */
    public function logout() {
        // Allow to log out only when user is logged in.
        if ($this->authService->getIdentity() == null)
            throw new \Exception('The user is not logged in');

        // Remove identity form session.
        $this->authService->clearIdentity();
    }

    /**
     * This is a simple access control filter. It is able to restrict unauthorized
     * users to visit certain pages.
     *
     * This method uses the 'access_filter' key in the config file and determines
     * whether the current visitor is allowed to access the given controller action
     * or not. It returns true if allowed; otherwise false.
     * @param $controllerName
     * @param $actionName
     * @return bool
     * @throws \Exception
     */
    public function filterAccess($controllerName, $actionName) {
        // Determine mode - 'restrictive' (default) or 'permissive'. In restrictive
        // mode all controller actions must be explicitly listed under the 'access_filter'
        // config key, and access is denied to any not listed action for unauthorized users.
        // In permissive mode, if an action is not listed under the 'access_filter' key,
        // access to it is permitted to anyone (even for not logged in users.
        // Restrictive mode is more secure and recommended to use.
        $mode = isset($this->config['options']['mode']) ? $this->config['options']['mode'] : 'restrictive';
        if ($mode != 'restrictive' && $mode != 'permissive')
            throw new \Exception('Invalid filter access mode (expected either restrictive or permissive mode)');

        if (isset($this->config['controllers'][$controllerName])) {
            $items = $this->config['controllers'][$controllerName];
            foreach ($items as $item) {
                $actionList = $item['actions'];
                $allow = $item['allow'];
                if (is_array($actionList) && in_array($actionName, $actionList) ||
                    $actionList == '*') {
                    if ($allow == '*')
                        return true; // Anyone is allowed to see the page.
                    elseif ($allow == '@' && $this->authService->hasIdentity()) {
                        return true; // Only authenticated user is allowed to see the page.
                    } else {
                        return false; // Access denied.
                    }
                }
            }
        }

        // In restrictive mode, we forbid access for authenticated users to any
        // action not listed under 'access_filter' key (for security reasons).
        if ($mode == 'restrictive' && !$this->authService->hasIdentity())
            return false;

        // Permit access to this page.
        return true;
    }
}
```
## AuthManagerFactory
```php
namespace User\Service\Factory;

use Interop\Container\ContainerInterface;
use User\Service\AuthManager;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\Session\SessionManager;

class AuthManagerFactory implements FactoryInterface {

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        // Instantiate dependencies.
        $sessionManager = $container->get(SessionManager::class);
        $authenticationService = $container->get(AuthenticationService::class);

        // Get contents of 'access_filter' config key (the AuthManager service
        // will use this data to determine whether to allow currently logged in user
        // to execute the controller action or not.
        $config = $container->get('Config');
        if (isset($config['access_filter']))
            $config = $config['access_filter'];
        else
            $config = [];

        // Instantiate the AuthManager service and inject dependencies to its constructor.
        return new AuthManager($sessionManager, $authenticationService, $config);
    }
}
```
## AuthController
This file is used to handle login/logout page by using `AuthManager`.
```php
namespace User\Controller;

use Doctrine\ORM\EntityManager;
use User\Form\LoginForm;
use User\Service\AuthManager;
use User\Service\UserManager;
use Zend\Authentication\Result;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\Uri\Uri;
use Zend\View\Model\ViewModel;

/**
 * This controller is responsible for letting the user to log in and log out.
 *
 * Class AuthController
 * @package User\Controller
 */
class AuthController extends AbstractActionController {

    /**
     * EntityManager
     * @var EntityManager
     */
    private $entityManager;

    /**
     * Auth Manager.
     * @var AuthManager
     */
    private $authManager;

    /**
     * User manager.
     * @var UserManager
     */
    private $userManager;

    /**
     * AuthController constructor.
     * @param $entityManager
     * @param $authManager
     * @param $userManager
     */
    public function __construct(
        $entityManager,
        $authManager,
        $userManager
    ) {
        $this->entityManager = $entityManager;
        $this->authManager = $authManager;
        $this->userManager = $userManager;
    }

    /**
     * @return ViewModel
     * @throws \Exception
     */
    public function loginAction() {
        // Retrieve the redirect URL (if passed). We will redirect the user to this
        // URL after successful login.
        $redirectUrl = (string) $this->params()->fromQuery('redirectUrl', '');
        if (strlen($redirectUrl) > 2048)
            throw new \Exception("Too long redirectUrl argument passed.");

        // Check if we do not have users in database at all. If so, create
        // the 'Admin' user.
        $this->userManager->createAdminUserIfNotExists();

        // Create login form
        $form = new LoginForm();
        $form->get('redirect_url')->setValue($redirectUrl);

        // Store login status
        $isLoginError = false;

        // Check if user has submitted the form
        if ($this->getRequest()->isPost()) {

            // Fill in the form with POST data
            $data = $this->params()->fromPost();

            $form->setData($data);

            // Validate form
            if ($form->isValid()) {

                // Get filtered and validated data
                $data = $form->getData();

                // Perform login attempt.
                $result = $this->authManager->login($data['email'], $data['password'], $data['remember_me']);

                // Check result
                if ($result->getCode() == Result::SUCCESS) {

                    // Get redirect URL.
                    $redirectUrl = $this->params()->fromPost('redirect_url', '');

                    if (!empty($redirectUrl)) {
                        // The below check is to prevent possible redirect attack
                        // (if someone tries to redirect user to another domain).
                        $uri = new Uri($redirectUrl);
                        if (!$uri->isValid() || $uri->getHost() != null)
                            throw new \Exception('Incorrect redirect URL: ' . $redirectUrl);
                    }

                    // If redirect URL is provided, redirect the user to that URL;
                    // otherwise redirect to Home page.
                    if (empty($redirectUrl)) {
                        return $this->redirect()->toRoute('home');
                    } else {
                        $this->redirect()->toUrl($redirectUrl);
                    }
                } else {
                    $isLoginError = true;
                }
            } else {
                $isLoginError = true;
            }
        }

        return new ViewModel([
            'form' => $form,
            'isLoginError' => $isLoginError,
            'redirectUrl' => $redirectUrl
        ]);
    }

    /**
     * The "logout" action performs logout operation.
     *
     * @return \Zend\Http\Response
     * @throws \Exception
     */
    public function logoutAction() {
        $this->authManager->logout();

        return $this->redirect()->toRoute('login');
    }

    /**
     * Displays the "Not Authorized" page.
     * @return ViewModel
     */
    public function notAuthorizedAction() {
        $this->getResponse()->setStatusCode(403);
        return new ViewModel();
    }
}
```
## AuthControllerFactory
Use for init the `AuthController`.
```php
namespace User\Controller\Factory;

use Interop\Container\ContainerInterface;
use User\Controller\AuthController;
use User\Service\AuthManager;
use User\Service\UserManager;
use Zend\ServiceManager\Factory\FactoryInterface;

/**
 * This is the factory for AuthController. Its purpose is to instantiate the controller
 * and inject dependencies into its constructor.
 * @package User\Controller\Factory
 */
class AuthControllerFactory implements FactoryInterface {
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $entityManager = $container->get('doctrine.entitymanager.orm_default');
        $authManager = $container->get(AuthManager::class);
        $userManager = $container->get(UserManager::class);

        return new AuthController($entityManager, $authManager, $userManager);
    }
}
```
# Extendable Capability
You can custom `Adapter`, `Storage` with interface `Zend\Authentication\Adapter` and `Zend\Authentication\Storage\StorageInterface`
# Supported Adapter
- `Zend\Authentication\Adapter\Callback`
- `Zend\Authentication\Adapter\Http`
- `Zend\Authentication\Adapter\Digest`
- `Zend\Authentication\Adapter\Ldap`
- `Zend\Authentication\Adapter\DbTable`
# Support Storage
- `Zend\Authentication\Storage\Session`
- `Zend\Authentication\Storage\Chain`
- `Zend\Authentication\Storage\NonPersistent`

以上是关于markdown Zend Framework 3:身份验证的主要内容,如果未能解决你的问题,请参考以下文章

markdown Zend Framework 3:授权和RBAC

markdown Zend Framework 3:Controller插件管理器

markdown Zend Framework 3:RBAC的访问过滤器和访问视图助手

markdown Zend Framework 3:用于身份验证服务的CurrentUser Filter和CurrentUser View Helper

如何模拟 Zend\Form 提交而不在 Zend Framework 2/3 中显示表单?

如何模拟Zend Form提交而不在Zend Framework 2/3中显示表单?