授权中间件

授权作为中间件应用于您的应用程序。 AuthorizationMiddleware 处理以下职责

  • 使用装饰器装饰请求的 ‘identity’,该装饰器添加 cancanResultapplyScope 方法(如果需要)。

  • 确保请求中已检查/绕过授权。

要使用中间件,请在您的应用程序类中实现 AuthorizationServiceProviderInterface。然后将您的应用程序实例传递到中间件,并将中间件添加到队列中。

一个基本示例是

namespace App;

use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;
use Cake\Http\BaseApplication;

class Application extends BaseApplication implements AuthorizationServiceProviderInterface
{
    public function getAuthorizationService(ServerRequestInterface $request, ResponseInterface $response)
    {
        $resolver = new OrmResolver();

        return new AuthorizationService($resolver);
    }

    public function middleware($middlewareQueue)
    {
        // other middleware
        $middlewareQueue->add(new AuthorizationMiddleware($this));

        return $middlewareQueue;
    }
}

授权服务需要策略解析器。有关可用解析器以及如何使用它们的说明,请参阅 策略 文档。

身份装饰器

默认情况下,请求中的 identity 将使用 Authorization\IdentityDecorator 进行装饰(包装)。装饰器类代理方法调用、数组访问和属性访问到装饰的身份对象。要直接访问底层身份,请使用 getOriginalData()

$originalUser = $user->getOriginalData();

如果您的应用程序使用 cakephp/authentication 插件,则将使用 Authorization\Identity 类。该类除了实现 Authorization\IdentityInterface 之外,还实现了 Authentication\IdentityInterface。这使您可以使用 Authentication 库的组件和助手来获取装饰后的身份。

使用您的 User 类作为身份

如果您有现有的 User 或身份类,您可以通过实现 Authorization\IdentityInterface 并使用 identityDecorator 中间件选项来跳过装饰器。首先,让我们更新我们的 User

namespace App\Model\Entity;

use Authorization\AuthorizationServiceInterface;
use Authorization\IdentityInterface;
use Authorization\Policy\ResultInterface;
use Cake\ORM\Entity;

class User extends Entity implements IdentityInterface
{
    /**
     * @inheritDoc
     */
    public function can(string $action, mixed $resource): bool
    {
        return $this->authorization->can($this, $action, $resource);
    }

    /**
     * @inheritDoc
     */
    public function canResult(string $action, mixed $resource): ResultInterface
    {
        return $this->authorization->canResult($this, $action, $resource);
    }

    /**
     * @inheritDoc
     */
    public function applyScope(string $action, mixed $resource, mixed ...$optionalArgs): mixed
    {
        return $this->authorization->applyScope($this, $action, $resource, ...$optionalArgs);
    }

    /**
     * @inheritDoc
     */
    public function getOriginalData(): \ArrayAccess|array
    {
        return $this;
    }

    /**
     * Setter to be used by the middleware.
     */
    public function setAuthorization(AuthorizationServiceInterface $service)
    {
        $this->authorization = $service;

        return $this;
    }

    // Other methods
}

现在我们的用户实现了必要的接口,让我们更新我们的中间件设置

// In your Application::middleware() method;

// Authorization
$middlewareQueue->add(new AuthorizationMiddleware($this, [
    'identityDecorator' => function ($auth, $user) {
        return $user->setAuthorization($auth);
    }
]));

您不再需要更改任何现有的类型提示,并且可以开始在您有权访问用户的地方使用授权策略。

如果您还使用 Authentication 插件,请确保实现这两个接口。

use Authorization\IdentityInterface as AuthorizationIdentity;
use Authentication\IdentityInterface as AuthenticationIdentity;

class User extends Entity implements AuthorizationIdentity, AuthenticationIdentity
{
    ...

    /**
     * Authentication\IdentityInterface method
     *
     * @return string
     */
    public function getIdentifier()
    {
        return $this->id;
    }

    ...
}

确保应用授权

默认情况下,AuthorizationMiddleware 将确保每个包含 identity 的请求都已检查/绕过授权。如果未检查授权,则会引发 AuthorizationRequiredException。此异常是在您的其他中间件/控制器操作完成后引发的,因此您不能依赖它来阻止未经授权的访问,但它在开发/测试期间是一个有用的帮助。您可以通过选项禁用此行为

$middlewareQueue->add(new AuthorizationMiddleware($this, [
    'requireAuthorizationCheck' => false
]));

处理未经授权的请求

默认情况下,应用程序抛出的授权异常由中间件重新抛出。您可以为未经授权的请求配置处理程序并执行自定义操作,例如将用户重定向到登录页面。

内置处理程序为

  • Exception - 此处理程序将重新抛出异常,这是中间件的默认行为。

  • Redirect - 此处理程序将请求重定向到提供的 URL。

  • CakeRedirect - 支持 CakePHP 路由器的重定向处理程序。

这两个重定向处理程序共享相同的配置选项

  • url - 要重定向到的 URL(CakeRedirect 支持 CakePHP 路由器语法)。

  • exceptions - 应该重定向的异常类列表。默认情况下,只有 MissingIdentityException 被重定向。

  • queryParam - 访问的请求 URL 将附加到重定向 URL 的查询参数(默认情况下为 redirect)。

  • statusCode - 重定向的 HTTP 状态代码,默认情况下为 302

例如

use Authorization\Exception\MissingIdentityException;

$middlewareQueue->add(new AuthorizationMiddleware($this, [
    'unauthorizedHandler' => [
        'className' => 'Authorization.Redirect',
        'url' => '/pages/unauthorized',
        'queryParam' => 'redirectUrl',
        'exceptions' => [
            MissingIdentityException::class,
            OtherException::class,
        ],
    ],
]));

所有处理程序都将抛出的异常对象作为参数传递。此异常始终是 Authorization\Exception\Exception 的实例。在这个示例中,Authorization.Redirect 处理程序只为您提供指定要侦听哪些异常的选项。

因此,在这个我们使用 Authorization.Redirect 处理程序的示例中,如果我们想优雅地处理它们,我们可以将其他基于 Authorization\Exception\Exception 的异常添加到 execeptions 数组中

'exceptions' => [
    MissingIdentityException::class,
    ForbiddenException::class
],

请参阅 RedirectHandler 源代码

配置选项作为最后一个参数传递给处理程序的 handle() 方法。

在被未经授权的请求重定向后添加闪存消息

目前没有直接的方法可以将闪存消息添加到未经授权的重定向中。

因此,您需要创建自己的处理程序,该处理程序添加闪存消息(或您希望在重定向时发生的任何其他逻辑)

如何创建自定义 UnauthorizedHandler

  1. 创建此文件 src/Middleware/UnauthorizedHandler/CustomRedirectHandler.php

    <?php
    declare( strict_types = 1 );
    
    namespace App\Middleware\UnauthorizedHandler;
    
    use Authorization\Exception\Exception;
    use Authorization\Middleware\UnauthorizedHandler\RedirectHandler;
    use Psr\Http\Message\ResponseInterface;
    use Psr\Http\Message\ServerRequestInterface;
    
    class CustomRedirectHandler extends RedirectHandler {
        public function handle( Exception $exception, ServerRequestInterface $request, array $options = [] ): ResponseInterface {
            $response = parent::handle( $exception, $request, $options );
            $request->getFlash()->error( 'You are not authorized to access that location' );
            return $response;
        }
    }
    
  2. 告诉 AuthorizationMiddleware 应该使用您的新的自定义处理程序

    // in your src/Application.php
    
    use Authorization\Exception\MissingIdentityException;
    use Authorization\Exception\ForbiddenException;
    
    $middlewareQueue->add(new AuthorizationMiddleware($this, [
        'unauthorizedHandler' => [
            'className' => 'CustomRedirect', // <--- see here
            'url' => '/users/login',
            'queryParam' => 'redirectUrl',
            'exceptions' => [
                MissingIdentityException::class,
                ForbiddenException::class
            ],
            'custom_param' => true,
        ],
    ]));
    

如您所见,您仍然拥有与使用 Authorization.Redirect 作为 className 一样配置参数。

这是因为我们基于插件中提供的 RedirectHandler 扩展了我们的处理程序。因此,所有这些功能都存在,并且我们在 handle() 函数中拥有自己的功能。

如果您希望向您的功能添加更多配置参数,则 custom_param 会出现在您在 CustomRedirectHandler 中的 handle() 函数中提供的 $options 数组中。

您可以查看 CakeRedirectHandlerRedirectHandler 来了解这样的处理程序应该是什么样子。