zfr/zfr-oauth2-server

用于创建OAuth 2服务器的PHP库

0.10.0 2022-08-30 12:17 UTC

README

Continuous Integration Latest Stable Version Coverage Status Total Downloads Gitter

ZfrOAuth2Server是一个实现OAuth 2规范的PHP库。其主要目标是成为一个干净、PHP 7.0+库,旨在与任何持久层一起使用。它与PSR-7请求和响应兼容,这使得它可以与任何兼容PSR-7的框架一起使用。

目前,ZfrOAuth2Server没有实现整个规范(缺少隐式授权),因此如果您想了解ZfrOAuth2Server是否适用于您的应用程序,请查看文档。

然而,它实现了额外的令牌撤销规范。

以下是可以使用的其他OAuth2库

要求

  • PHP 7.4或更高版本

待办事项

  • 编写文档
  • 安全审计
  • 审查整个规范
  • 更广泛地测试授权服务器
  • 添加隐式授权

版本说明

请注意,直到我们达到1.0,我们将不会遵循语义版本。这意味着在0.1.x和0.2.x版本之间可能会发生BC变化。

完全重写的当前预发布版本不与先前实现兼容 - 考虑为EOL - 请参阅legacy-0.7分支。

请参阅变更日志

安装

使用Composer安装

php composer.phar require zfr/zfr-oauth2-server:^0.9-beta

支持

配置

一些Apache模块会删除HTTP授权头,如Authorization,试图通过防止脚本看到敏感信息来增强安全性,除非开发人员明确启用此功能。

许多这些模块允许添加以下行到 .htaccess(或vhost目录指令)来允许这些头。

CGIPassAuth on

Apache 2.4.13以来

文档

ZfrOAuth2Server基于RFC 6749文档。

为什么使用OAuth2?

OAuth2是一种身份验证/授权系统,可以用于

  • 实现无状态的身份验证机制(适用于API)
  • 允许第三方安全地连接到您的应用程序
  • 通过使用作用域来保护您的应用程序

OAuth2是一个密集、可扩展的规范,可以用于多种用途。截至今天,ZfrOAuth2Server实现了四个官方授权中的三个:AuthorizationGrant、ClientCredentialsGrant、PasswordGrant。此外还提供了一个RefreshTokenGrant来获取新的访问令牌。即将推出隐式授权和JWT令牌(需要帮助)。

OAuth2是如何工作的?

本文档旨在不详细解释OAuth2的工作原理。这里有一篇很好的参考资料您可以阅读。然而,这里是OAuth2工作原理的基本概念

  1. 资源所有者(您的JavaScript API、您的移动应用程序等)向授权服务器请求所谓的“访问令牌”。根据用例的不同,有几种策略,这些策略被称为“授权”。例如,“密码授权”假定资源所有者发送其用户名/密码。在所有情况下,您的授权服务器都会响应一个访问令牌(以及一个可选的刷新令牌)。
  2. 客户端将此访问令牌发送到对您的API进行的每个请求。由“资源服务器”使用它将此访问令牌映射到您系统中的用户。

选择授权类型取决于您的应用程序。以下是一些有关如何选择的提示

  • 如果您是您API的唯一消费者(例如,您的JavaScript应用程序调用您的API),您应该使用“密码授权”。因为您信任您的应用程序,发送用户名/密码不是问题。
  • 如果您想第三方代码连接到您的API,并且您确信该第三方可以保守秘密(这意味着客户端不是JavaScript API或移动应用程序):您可以使用客户端凭据授权。
  • 如果您想第三方代码连接到您的API,而这些第三方应用程序无法保守秘密(例如,考虑一个非官方的Twitter客户端连接到您的Twitter账户),您应该使用授权授权。

使用授权服务器

授权服务器的目标是接受请求并生成令牌。授权服务器可以拒绝请求(例如,如果参数缺失或用户名/密码不正确)。

要使用授权服务器,您必须首先决定您想要支持哪种授权。一些应用程序可能只支持一种类型的授权,而其他应用程序可能支持所有可用的授权。这完全取决于您,您应该首先对这些授权有一个坚实的基础理解。例如,以下是创建仅支持授权的授权服务器的方法

$authTokenService    = new TokenService($objectManager, $authTokenRepository, $scopeRepository);
$accessTokenService  = new TokenService($objectManager, $accessTokenRepository, $scopeRepository);
$refreshTokenService = new TokenService($objectManager, $refreshTokenRepository, $scopeRepository);

$authorizationGrant  = new AuthorizationGrant($authTokenService, $accessTokenService, $refreshTokenService);
$authorizationServer = new AuthorizationServer([$authorizationGrant]);

// Response contains the various parameters you can return
$response = $authorizationServer->handleRequest($request);

请求必须是一个有效的Psr\Http\Message\ServerRequestInterface,授权服务器返回一个符合OAuth2规范的Psr\Http\Message\ResponseInterface对象。

传递用户

大多数时候,您想要将访问令牌与用户关联起来。这是将令牌映射到您系统用户的唯一方法。为此,您可以将一个可选的第二个参数传递给handleRequest。此类必须实现ZfrOAuth2\Server\Model\TokenOwnerInterface接口

$user = new User(); // must implement TokenOwnerInterface

// ...

$response = $authorizationServer->handleRequest($request, $user);

AuthorizationServerMiddleware能够为您完成此操作并从(可配置的)请求属性中检索用户实例。这取决于您提供具有更高优先级的中间件,以将TokenOwnerInterface实例添加到请求属性中。

此类实现示例使用了LaminasAuthentication和Mezzio的TemplateRenderer。

final class OAuth2AuthorizationFlow
{
    /**
     * @var AuthenticationService
     */
    private $authenticationService;

    /**
     * @var ClientService
     */
    private $clientService;

    /**
     * @var TemplateRendererInterface
     */
    private $template;

    public function __construct(
        AuthenticationService $authenticationService,
        ClientService $clientService,
        TemplateRendererInterface $template
    ) {
        $this->authenticationService = $authenticationService;
        $this->clientService         = $clientService;
        $this->template              = $template;
    }

    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $out = null)
    {
        if ($this->authenticationService->hasIdentity()) {
            $request = $request->withAttribute('owner', $this->authenticationService->getIdentity());
        }

        if ($request->getMethod() === 'POST') {
            $post     = $request->getParsedBody();
            $approved = filter_var($post['approved'], FILTER_VALIDATE_BOOLEAN);

            if ($approved) {
                return $out($request, $response);
            }
        }

        $data  = [];
        $query = $request->getUri()->getQuery();
        parse_str($query, $data['query']);

        $data['client'] = $this->clientService->getClient($data['query']['client_id']);

        return new HtmlResponse($this->template->render('app::oauth2/authorize-request', $data));
    }
}

撤销令牌

ZfrOAuth2Server支持使用RFC 7009规范撤销访问和刷新令牌。您可以在授权服务器中使用handleRevocationRequest方法。您必须传递以下两个POST参数

  • token:要删除的令牌(无论是访问令牌还是刷新令牌)
  • token_hint_type:必须是access_tokenrefresh_token,以指示授权服务器撤销哪种类型的令牌。

如果您需要撤销为非公开客户端(这意味着具有秘密密钥的客户端)颁发的令牌,那么您必须使用客户端ID和密钥进行请求认证。

如果尝试撤销一个不存在的令牌,根据规范,它将返回200 SUCCESS请求。然而,如果令牌有效,但由于任何原因(例如数据库宕机)无法删除,则返回503 SERVICE UNAVAILABLE错误!

使用资源服务器

您可以使用资源服务器来检索访问令牌(通过自动从HTTP头中提取数据)。您还可以在检索令牌时指定作用域约束。

$accessTokenService = new TokenService($objectManager, $accessTokenRepository, $scopeRepository);
$resourceServer     = new ResourceServer($accessTokenService);

if (!$token = $resourceServer->getAccessToken($request, ['write']) {
    // there is either no access token, or the access token is expired, or the access token does not have
    // the `write` scope
}

ResourceServerMiddleware能够为您完成这项工作,只需在所有其他中间件之前运行它。

示例mezzio expressive路由配置。

[
            'name'            => 'command::commerce::create-store',
            'path'            => '/commerce/create-store',
            'middleware'      => [
                ResourceServerMiddleware::class,
                MyActionMiddleware::class,
            ],
            'allowed_methods' => ['OPTIONS', 'POST'],
        ],

持久层

截至版本0.8-beta1,ZfrOAuth2Server已被重写以实现持久层无关。这意味着它可以与任何首选的持久层一起使用。

目前这些包提供了持久层;