zfcampus/zf-mvc-auth

此包已被废弃且不再维护。作者建议使用laminas-api-tools/api-tools-mvc-auth包。

ZF2 模块,提供身份验证和授权事件及基础设施


README

仓库废弃于2019-12-31

此仓库已迁移至laminas-api-tools/api-tools-mvc-auth

Build Status Coverage Status

简介

zf-mvc-auth 是一个 ZF2 模块,它通过添加服务、事件和配置来扩展 ZF2 MVC 生命周期,以处理身份验证和授权。

对于身份验证,默认支持三种主要方法:HTTP Basic 身份验证、HTTP Digest 身份验证和 OAuth2(这需要 Brent Shaffer 的 OAuth2 Server)。

对于授权,该模块提供了一个预调度时间监听器,该监听器将确定给定的路由是否匹配,以及 HTTP 方法是否被授权进行调度。

要求

请参阅composer.json文件。

安装

运行以下composer命令

$ composer require "zfcampus/zf-mvc-auth"

或者,手动将以下内容添加到您的composer.json文件中的require部分

"require": {
    "zfcampus/zf-mvc-auth": "^1.4"
}

然后运行composer update以确保安装了该模块。

最后,将模块名称添加到您的项目的config/application.config.php文件中的modules键下

return [
    /* ... */
    'modules' => [
        /* ... */
        'ZF\MvcAuth',
    ],
    /* ... */
];

配置

用户配置

此模块用户配置的最高级别配置键是zf-mvc-auth。在此键下有两个子键,一个是authentication,另一个是authorization

键:authentication

authentication键用于与身份验证过程或验证身份的过程相关的任何配置。

子键:http

http子键用于配置基于 HTTP 的身份验证方案。这些方案使用 ZF2 的Zend\Authentication\Adapter\Http适配器,该适配器实现了 HTTP Basic 和 HTTP Digest 身份验证。为此,HTTP 适配器使用基于文件的“解析器”来解析包含凭证的文件。这些实现细节可以在ZF2 手册中的身份验证部分中找到。

http子键有几个字段

  • accept_schemes: 必需;配置的方案数组;basicdigest之一或两个。
  • realm: 必需;这通常是一个标识 HTTP 域的字符串;例如,“我的网站”。
  • digest_domains: 必需(对于 HTTP Digest);这是受保护区域的相对 URI,通常是/
  • nonce_timeout: 对于HTTP摘要,是必需的;摘要nonce失效的秒数,通常为3600

除了这些配置选项之外,以下配置中的一项或两项是必需的

  • htpasswd:指向一个以htpasswd格式创建的文件的路径
  • htdigest:指向一个以htdigest格式创建的文件的路径

示例可能如下所示

'http' => [
    'accept_schemes' => ['basic', 'digest'],
    'realm' => 'My Web Site',
    'digest_domains' => '/',
    'nonce_timeout' => 3600,
    'htpasswd' => APPLICATION_PATH . '/data/htpasswd', // htpasswd tool generated
    'htdigest' => APPLICATION_PATH . '/data/htdigest', // @see http://www.askapache.com/online-tools/htpasswd-generator/
],
子键:map
  • 自1.1.0版本起。

map子键用于将API模块(可选地,带有版本命名空间)映射到指定的认证类型(通常是basicdigestoauth2之一)。这可以用于对不同API或同一API的不同版本实施不同的认证方法。

return [
    'zf-mvc-auth' => [
        'authentication' => [
            'map' => [
                'Status\V1' => 'basic',  // v1 only!
                'Status\V2' => 'oauth2', // v2 only!
                'Ping'      => 'digest', // all versions!
            ],
        ],
    ],
];

如果没有map子键,如果定义了任何认证适配器配置,则该配置将用于任何API。

注意:从1.0迁移的用户:在1.0系列中,认证是按应用程序进行的,而不是按API。迁移到1.1应该是无缝的;如果您未编辑您的认证设置,或向任何API提供认证信息,则您的API将继续按以前的方式运行。您第一次执行这些操作之一时,Admin API将创建一个映射,将每个服务的每个版本映射到配置的认证方案,从而确保您的API继续按先前配置的方式运行,同时让您能够将来按API和版本定义认证。

子键:types
  • 自1.1.0版本起。

从1.1.0版本开始,提供了认证适配器的概念。适配器“提供”一个或多个认证类型;然后这些类型被用于内部确定使用哪个适配器,以及Admin API将API映射到特定的认证类型。

在某些情况下,您可能正在使用监听器或其他用于认证API的设施。为了允许将这些映射(在这种情况下主要是文档功能),存在types子键。此键是字符串认证类型的数组

return [
    'zf-mvc-auth' => [
        'authentication' => [
            'types' => [
                'token',
                'key',
            ],
        ],
    ],
];

此键及其内容必须手动创建。

子键:adapters
  • 自1.1.0版本起。

从1.1.0版本开始,随着适配器的引入,您还可以配置命名的HTTP和OAuth2适配器。提供的名称将被用作映射API到认证适配器的认证类型。

adapters键的格式是键/值对,其中键用作类型,值用作提供ZF\MvcAuth\Authentication\HttpAdapterZF\MvcAuth\Authentication\OAuth2Adapter实例的配置,如下所示

return [
    'zf-mvc-auth' => [
        'authentication' => [
            'adapters' => [
                'api' => [
                    // This defines an HTTP adapter that can satisfy both
                    // basic and digest.
                    'adapter' => 'ZF\MvcAuth\Authentication\HttpAdapter',
                    'options' => [
                        'accept_schemes' => ['basic', 'digest'],
                        'realm' => 'api',
                        'digest_domains' => 'https://example.com',
                        'nonce_timeout' => 3600,
                        'htpasswd' => 'data/htpasswd',
                        'htdigest' => 'data/htdigest',
                    ],
                ],
                'user' => [
                    // This defines an OAuth2 adapter backed by PDO.
                    'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter',
                    'storage' => [
                        'adapter' => 'pdo',
                        'dsn' => 'mysql:host=localhost;dbname=oauth2',
                        'username' => 'username',
                        'password' => 'password',
                        'options' => [
                            1002 => 'SET NAMES utf8', // PDO::MYSQL_ATTR_INIT_COMMAND
                        ],
                    ],
                ],
                'client' => [
                    // This defines an OAuth2 adapter backed by Mongo.
                    'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter',
                    'storage' => [
                        'adapter' => 'mongo',
                        'locator_name' => 'SomeServiceName', // If provided, pulls the given service
                        'dsn' => 'mongodb://localhost',
                        'database' => 'oauth2',
                        'options' => [
                            'username' => 'username',
                            'password' => 'password',
                            'connectTimeoutMS' => 500,
                        ],
                    ],
                ],
            ],
        ],
    ],
];

键:authorization

子键:deny_by_default

deny_by_default切换了Zend\Permissions\Acl实现的默认行为。默认值是false,这意味着如果没有认证用户存在,并且没有权限规则适用于当前资源,则允许访问。将此设置更改为true以默认要求认证身份。

示例

'deny_by_default' => false,
使用zf-oauth2的deny_by_default

当使用deny_by_default => truezf-oauth2一起使用时,您需要显式允许OAuth2控制器的POST,以便进行认证请求。

以下是一个示例

'authorization' => [
    'deny_by_default' => true,
    'ZF\\OAuth2\\Controller\\Auth' => [
        'actions' => [
            'token' => [
                'GET'    => false,
                'POST'   => true,   // <-----
                'PATCH'  => false,
                'PUT'    => false,
                'DELETE' => false,
            ],
        ],
    ],
],

子键:控制器服务名称

authorization 键下是一个由 控制器服务名称 作为键的授权配置设置数组。这些数组的结构取决于您尝试授予或限制访问的控制器服务的类型。

对于典型的基于 ZF2 的动作控制器,此数组以 actions 作为键。在此键下,给定控制器服务的每个动作名称都与一个 权限数组 关联。

对于基于 zf-rest 的控制器,使用顶级键 collectionentity。在这些键下将会有一个相关的 权限数组

权限数组 由键为数组的 default 或 HTTP 方法组成。这些值将是布尔值,其中 true 表示 需要认证用户,而 false 表示 不需要认证用户。如果一个动作或 HTTP 方法没有被标识,将假定 default 值。如果没有默认值,将假定上面讨论的 deny_by_default 键的行为。

下面是一个示例

'authorization' => [
    'Controller\Service\Name' => [
        'actions' => [
            'action' => [
                'default' => boolean,
                'GET' => boolean,
                'POST' => boolean,
                // etc.
            ],
        ],
        'collection' => [
            'default' => boolean,
            'GET' => boolean,
            'POST' => boolean,
            // etc.
        ],
        'entity' => [
            'default' => boolean,
            'GET' => boolean,
            'POST' => boolean,
            // etc.
        ],
    ],
],

系统配置

以下配置在 config/module.config.php 中提供,以启用模块的功能

'service_manager' => [
    'aliases' => [
        'authentication' => 'ZF\MvcAuth\Authentication',
        'authorization' => 'ZF\MvcAuth\Authorization\AuthorizationInterface',
        'ZF\MvcAuth\Authorization\AuthorizationInterface' => 'ZF\MvcAuth\Authorization\AclAuthorization',
    ],
    'factories' => [
        'ZF\MvcAuth\Authentication' => 'ZF\MvcAuth\Factory\AuthenticationServiceFactory',
        'ZF\MvcAuth\ApacheResolver' => 'ZF\MvcAuth\Factory\ApacheResolverFactory',
        'ZF\MvcAuth\FileResolver' => 'ZF\MvcAuth\Factory\FileResolverFactory',
        'ZF\MvcAuth\Authentication\DefaultAuthenticationListener' => 'ZF\MvcAuth\Factory\DefaultAuthenticationListenerFactory',
        'ZF\MvcAuth\Authentication\AuthHttpAdapter' => 'ZF\MvcAuth\Factory\DefaultAuthHttpAdapterFactory',
        'ZF\MvcAuth\Authorization\AclAuthorization' => 'ZF\MvcAuth\Factory\AclAuthorizationFactory',
        'ZF\MvcAuth\Authorization\DefaultAuthorizationListener' => 'ZF\MvcAuth\Factory\DefaultAuthorizationListenerFactory',
        'ZF\MvcAuth\Authorization\DefaultResourceResolverListener' => 'ZF\MvcAuth\Factory\DefaultResourceResolverListenerFactory',
    ],
    'invokables' => [
        'ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener' => 'ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener',
        'ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener' => 'ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener',
    ],
],

这些服务将在事件和服务部分中描述。

ZF2 事件

事件

ZF\MvcAuth\MvcAuthEvent::EVENT_AUTHENTICATION (也称为 "authentication")

此事件在 MvcEvent::EVENT_ROUTE500 优先级下触发。它通过 ZF\MvcAuth\MvcRouteListener 事件监听器聚合进行注册。

ZF\MvcAuth\MvcAuthEvent::EVENT_AUTHENTICATION_POST (也称为 "authentication.post")

此事件在 MvcEvent::EVENT_ROUTE499 优先级下触发。它通过 ZF\MvcAuth\MvcRouteListener 事件监听器聚合进行注册。

ZF\MvcAuth\MvcAuthEvent::EVENT_AUTHORIZATION (也称为 "authorization")

此事件在 MvcEvent::EVENT_ROUTE-600 优先级下触发。它通过 ZF\MvcAuth\MvcRouteListener 事件监听器聚合进行注册。

ZF\MvcAuth\MvcAuthEvent::EVENT_AUTHORIZATION_POST (也称为 "authorization.post")

此事件在 MvcEvent::EVENT_ROUTE-601 优先级下触发。它通过 ZF\MvcAuth\MvcRouteListener 事件监听器聚合进行注册。

ZF\MvcAuth\MvcAuthEvent 对象

当任何认证或授权事件被触发时,MvcAuthEvent 对象提供上下文信息。它保持以下内容

  • 身份:setIdentity()getIdentity()
  • 认证服务:setAuthentication()getAuthentication()
  • 授权服务:setAuthorization()getAuthorization()
  • 授权结果:setIsAuthorizedisAuthorized()
  • 原始 MVC 事件:getMvcEvent()

监听器

ZF\MvcAuth\Authentication\DefaultAuthenticationListener

该监听器附加到 MvcAuth::EVENT_AUTHENTICATION 事件。它主要负责执行任何身份验证,并确保已验证的身份在 MvcAuthEventMvcEvent 对象中持久化(后者在事件参数 ZF\MvcAuth\Identity 下)。

ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener

该监听器附加到 MvcAuth::EVENT_AUTHENTICATION_POST 事件。它主要负责确定是否执行了不成功的身份验证,如果是这种情况,它将尝试在 MvcEvent 的响应对象上设置 401 未授权 状态。

ZF\MvcAuth\Authorization\DefaultAuthorizationListener

该监听器附加到 MvcAuth::EVENT_AUTHORIZATION 事件。它主要负责在配置的授权服务上执行 isAuthorized() 方法。

ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener

该监听器附加到 MvcAuth::EVENT_AUTHORIZATION_POST 事件。它主要负责确定当前请求是否被授权。在当前请求未授权的情况下,它将尝试在 MvcEvent 的响应对象上设置 403 禁止访问 状态。

ZF\MvcAuth\Authorization\DefaultResourceResolverListener

该监听器附加到 MvcAuth::EVENT_AUTHENTICATION_POST,优先级为 -1。它主要负责在配合 zf-rest 模块使用时,为基于 zf-rest 的控制器创建和持久化一个特殊名称。

ZF2 服务

控制器插件

该模块公开了控制器插件 getIdentity(),映射到 ZF\MvcAuth\Identity\IdentityPlugin。该插件将返回在身份验证过程中发现的身份,并将其注入到 Zend\Mvc\MvcEventZF\MvcAuth\Identity 参数中。如果 MvcEvent 中不存在身份,或存在的是非 ZF\MvcAuth\Identity\IdentityInterface 实例的身份,则将返回一个 ZF\MvcAuth\Identity\GuestIdentity 实例。

事件监听器服务

以下服务提供并作为事件监听器使用

  • ZF\MvcAuth\Authentication\DefaultAuthenticationListener
  • ZF\MvcAuth\Authentication\DefaultAuthenticationPostListener
  • ZF\MvcAuth\Authorization\DefaultAuthorizationListener
  • ZF\MvcAuth\Authorization\DefaultAuthorizationPostListener
  • ZF\MvcAuth\Authorization\DefaultResourceResolverListener

ZF\MvcAuth\Authentication (即 "身份验证")

这是 Zend\Authentication\AuthenticationService 的一个实例。

ZF\MvcAuth\Authentication\AuthHttpAdapter

这是 Zend\Authentication\Adapter\Http 的一个实例。

ZF\MvcAuth\Authorization\AclAuthorization (即 "授权",ZF\MvcAuth\Authorization\AuthorizationInterface)

这是 ZF\MvcAuth\Authorization\AclAuthorization 的一个实例,它又扩展了 Zend\Permissions\Acl\Acl

ZF\MvcAuth\ApacheResolver

这是Zend\Authentication\Adapter\Http\ApacheResolver的一个实例。您可以通过提供自定义工厂来覆盖ApacheResolver,使用您自己的解析器。

ZF\MvcAuth\FileResolver

这是Zend\Authentication\Adapter\Http\FileResolver的一个实例。您可以通过提供自定义工厂来覆盖FileResolver,使用您自己的解析器。

认证适配器

  • 自1.1.0版本起

认证适配器提供了将自定义认证功能添加到API的最直接方法。适配器实现了ZF\MvcAuth\Authentication\AdapterInterface

namespace ZF\MvcAuth\Authentication;

use Zend\Http\Request;
use Zend\Http\Response;
use ZF\MvcAuth\Identity\IdentityInterface;
use ZF\MvcAuth\MvcAuthEvent;

interface AdapterInterface
{
    /**
     * @return array Array of types this adapter can handle.
     */
    public function provides();

    /**
     * Attempt to match a requested authentication type
     * against what the adapter provides.
     *
     * @param string $type
     * @return bool
     */
    public function matches($type);

    /**
     * Attempt to retrieve the authentication type based on the request.
     *
     * Allows an adapter to have custom logic for detecting if a request
     * might be providing credentials it's interested in.
     *
     * @param Request $request
     * @return false|string
     */
    public function getTypeFromRequest(Request $request);

    /**
     * Perform pre-flight authentication operations.
     *
     * Use case would be for providing authentication challenge headers.
     *
     * @param Request $request
     * @param Response $response
     * @return void|Response
     */
    public function preAuth(Request $request, Response $response);

    /**
     * Attempt to authenticate the current request.
     *
     * @param Request $request
     * @param Response $response
     * @param MvcAuthEvent $mvcAuthEvent
     * @return false|IdentityInterface False on failure, IdentityInterface
     *     otherwise
     */
    public function authenticate(Request $request, Response $response, MvcAuthEvent $mvcAuthEvent);
}

方法provides()应返回一个字符串数组,每个字符串都是一个此适配器提供的认证“类型”;例如,提供的ZF\MvcAuth\Authentication\HttpAdapter可以提供basic和/或digest

matches($type)方法应将给定的$type与适配器提供的类型进行比较,以确定是否可以处理认证请求。通常,这可以通过return in_array($type, $this->provides(), true);来完成

方法getTypeFromRequest()可以用来将传入的请求与它解析的认证类型(如果有)相匹配。示例可能包括解析Authorization头,或自定义头,如X-Api-Token

方法preAuth()可以用来提供客户端挑战;通常,这只会由包含的HttpAdapter使用。

最后,方法authenticate()用于尝试认证传入的请求。它应返回一个布尔值false,表示认证失败,或返回一个ZF\MvcAuth\Identity\IdentityInterface的实例;如果返回后者的实例,则该身份将在请求期间使用。

适配器附加到DefaultAuthenticationListener。要附加您自定义的适配器,您需要执行以下操作之一

  • 通过配置定义命名HTTP和/或OAuth2适配器。
  • 在事件监听器期间,获取您的适配器和DefaultAuthenticationListener服务,并将您的适配器附加到后者。
  • DefaultAuthenticationListener创建一个DelegatorFactory,在返回监听器之前附加您的自定义适配器。

定义命名的HTTP和/或OAuth2适配器

由于HTTP和OAuth2支持是内置的,因此zf-mvc-auth提供了一种配置驱动的创建这些类型命名适配器的方法。每个类型都需要在zf-mvc-auth.authentication.adapters配置下的唯一键,并且每种类型都有其自己的格式。

return [
    /* ... */
    'zf-mvc-auth' => [
        'authentication' => [
            'adapters' => [
                'api' => [
                    // This defines an HTTP adapter that can satisfy both
                    // basic and digest.
                    'adapter' => 'ZF\MvcAuth\Authentication\HttpAdapter',
                    'options' => [
                        'accept_schemes' => ['basic', 'digest'],
                        'realm' => 'api',
                        'digest_domains' => 'https://example.com',
                        'nonce_timeout' => 3600,
                        'htpasswd' => 'data/htpasswd',
                        'htdigest' => 'data/htdigest',
                    ],
                ],
                'user' => [
                    // This defines an OAuth2 adapter backed by PDO.
                    'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter',
                    'storage' => [
                        'adapter' => 'pdo',
                        'dsn' => 'mysql:host=localhost;dbname=oauth2',
                        'username' => 'username',
                        'password' => 'password',
                        'options' => [
                            1002 => 'SET NAMES utf8', // PDO::MYSQL_ATTR_INIT_COMMAND
                        ],
                    ],
                ],
                'client' => [
                    // This defines an OAuth2 adapter backed by Mongo.
                    'adapter' => 'ZF\MvcAuth\Authentication\OAuth2Adapter',
                    'storage' => [
                        'adapter' => 'mongo',
                        'locator_name' => 'SomeServiceName', // If provided, pulls the given service
                        'dsn' => 'mongodb://localhost',
                        'database' => 'oauth2',
                        'options' => [
                            'username' => 'username',
                            'password' => 'password',
                            'connectTimeoutMS' => 500,
                        ],
                    ],
                ],
            ],
            /* ... */
        ],
        /* ... */
    ],
    /* ... */
];

上面的配置将为您的应用程序提供认证类型['api-basic', 'api-digest', 'user', 'client'],这些类型可以与认证类型映射中的每个类型相关联。

如果您使用zf-apigility-admin的Admin API和/或Apigility UI配置认证适配器,上述配置将为您创建。

在事件监听器期间附加适配器

在这种情况下,最佳事件是“认证”事件。这样做时,您将希望将优先级设置为> 1,以确保它在DefaultAuthenticationListener之前执行。

在以下示例中,我们假设您已定义了一个名为MyCustomAuthenticationAdapter的服务,该服务返回一个实现AdapterInterface的实例,并且该类是您的API的Module类或应用程序中的模块。

class Module
{
    public function onBootstrap($e)
    {
        $app      = $e->getApplication();
        $events   = $app->getEventManager();
        $services = $app->getServiceManager();

        $events->attach(
            'authentication',
            function ($e) use ($services) {
                $listener = $services->get('ZF\MvcAuth\Authentication\DefaultAuthenticationListener')
                $adapter = $services->get('MyCustomAuthenticationAdapter');
                $listener->attach($adapter);
            },
            1000
        );
    }
}

通过返回空值,DefaultAuthenticationListener将继续执行,但现在也将具有新附加的适配器。

使用代理工厂

代理工厂是一种“装饰”由Zend Framework的ServiceManager返回的实例的方法,以便提供预条件或修改通常返回的实例。在我们的情况下,我们希望在实例创建后但在返回之前附加一个适配器。

在以下示例中,我们假设您已定义一个名为MyCustomAuthenticationAdapter的服务,该服务返回一个AdapterInterface实现。以下是一个为DefaultAuthenticationListener提供的代理工厂,它将注入我们的适配器。

use Zend\ServiceManager\DelegatorFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class CustomAuthenticationDelegatorFactory implements DelegatorFactoryInterface
{
    public function createDelegatorWithName(
        ServiceLocatorInterface $services,
        $name,
        $requestedName,
        $callback
    ) {
        $listener  = $callback();
        $listener->attach($services->get('MyCustomAuthenticationAdapter');
        return $listener;
    }
}

然后我们需要告诉ServiceManager关于代理工厂的信息;我们在模块的config/module.config.php文件或config/autoload/配置文件之一中这样做。

return [
    /* ... */
    'service_manager' => [
        /* ... */
        'delegators' => [
            'ZF\MvcAuth\Authentication\DefaultAuthenticationListener' => [
                'CustomAuthenticationDelegatorFactory',
            ],
        ],
    ],
    /* ... */
];

一旦配置完成,我们的适配器将被附加到检索到的每个DefaultAuthenticationListener实例上。