benjaminfavre/oauth2-http-client

一个轻量级的 OAuth 2 装饰器,用于 Symfony HTTP 客户端。

3.0.1 2022-11-15 12:38 UTC

This package is auto-updated.

Last update: 2024-08-31 00:31:34 UTC


README

这是一个用于 Symfony HTTP 客户端 的装饰器,它可以帮助您调用受 OAuth 2 保护的 API 端点。它处理与 OAuth 2 服务器的所有认证协议,让您专注于仅进行业务 API 调用。

设计得简约轻量,实际上没有依赖性,当然除了 Symfony Contracts 之外;并且它只需要 PHP JSON 扩展。

OAuth 2 是一个相对复杂的协议,提供了多种认证方式(在 OAuth 术语中称为“授权类型”)。这个装饰器的目标是提供所有标准授权类型。然而,您将要认证的 OAuth 2 服务器可能不会遵循严格的 OAuth 2 标准。这就是为什么装饰器被设计成可以自定义认证过程的每一步。

安装

composer require benjaminfavre/oauth2-http-client

用法

use Symfony\Component\HttpClient\HttpClient;
use BenjaminFavre\OAuthHttpClient\OAuthHttpClient;
use BenjaminFavre\OAuthHttpClient\GrantType\ClientCredentialsGrantType;

$httpClient = HttpClient::create(); 

// Here we will use the client credentials grant type but it could be any other grant type
$grantType = new ClientCredentialsGrantType(
    $httpClient,
    'https://github.com/login/oauth/access_token', // The OAuth server token URL
    'the-client-id',
    'the-client-password'
);

$httpClient = new OAuthHttpClient($httpClient, $grantType);

// Then use $httpClient to make your API calls:
// $httpClient->request(...);

它的工作原理

每次您发出 HTTP 请求时,装饰器将

  • 从缓存中获取访问令牌,如果缓存中没有,则从 OAuth 服务器获取;
  • 修改您的请求以添加访问令牌(通常在头部中);
  • 进行 API 调用并返回响应;
  • 如果第一次 API 调用失败是由于令牌过期,则可以尝试使用新的访问令牌再次尝试。

自定义

实现以下接口之一以自定义相关的认证步骤,并通过相关的装饰器设置器传递您类的实例。

GrantTypeInterface

一个负责从 OAuth 服务器获取访问令牌的类。装饰器自带四种标准授权类型

  • AuthorizationCodeGrantType;
  • ClientCredentialsGrantType;
  • PasswordGrantType;
  • RefreshTokenGrantType.

TokensCacheInterface

一个负责在缓存中存储和检索令牌的类。默认情况下,装饰器使用 MemoryTokensCache,它将令牌缓存在内存中。

RequestSignerInterface

一个负责修改 API 请求以添加访问令牌的类。默认情况下,装饰器使用 BearerHeaderRequestSigner,它在 Authorization 头部中添加访问令牌。您可以使用 HeaderRequestSigner 在其他头部中添加访问令牌,或者您可以实现接口进行更多自定义。

ResponseCheckerInterface

一个负责检查 API 调用是否由于令牌过期而失败的类。默认情况下,装饰器使用 StatusCode401ResponseChecker,它将 401 响应代码识别为需要续订访问令牌的信号。这可能导致误报(401 响应代码可能由于令牌过期之外的原因返回),因此如果您希望您的 OAuth 服务器返回可利用的细粒度错误原因,则可以实现该接口。

完整的 Symfony 特定示例

以下是如何在 Symfony 应用程序中使用此库的完整示例。

  • 自定义授权类型
  • 使用 Redis 作为缓存层
  • 使用 范围 HTTP 客户端定义
  • 使用 OAuth 服务器和 API 的不同 URL

首先,我们需要定义2个HTTP客户端:一个用于OAuth服务器,另一个用于API。

framework:
    http_client:
        scoped_clients:
            sharepoint_oauth.client:
                scope: '%env(resolve:SHAREPOINT_OAUTH_URL)%'
                headers:
                    Accept: 'application/json;odata=verbose'
                # other specific headers or settings if needed
            sharepoint_api.client:
                scope: '%env(resolve:SHAREPOINT_API_URL)%'
                headers:
                    Accept: 'application/json;odata=verbose'
                # other specific headers or settings if needed

其次,我们需要定义一个自定义的授权类型(Grant Type),它将从OAuth服务器获取访问令牌以连接到SharePoint,并使用上面定义的sharepoint_oauth.client

故意与内置的ClientCredentialsGrantType不同,以展示我们如何自定义认证过程。

<?php

declare(strict_types=1);

namespace App\Sharepoint;

use BenjaminFavre\OAuthHttpClient\GrantType\GrantTypeInterface;
use BenjaminFavre\OAuthHttpClient\GrantType\Tokens;
use BenjaminFavre\OAuthHttpClient\GrantType\TokensExtractor;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\HttpClient\HttpClientInterface;

final class CustomClientCredentialsGrantType implements GrantTypeInterface
{
    use TokensExtractor;

    public function __construct(
        private HttpClientInterface $client,
        private string $sharepointOauthClientId,
        private string $sharepointOauthClientSecret,
        private string $sharepointOauthUrl,
        private string $sharepointOauthResource,
    ) {
    }

    public function getTokens(): Tokens
    {
        $response = $this->client->request(Request::METHOD_POST, $this->sharepointOauthUrl, [
            'body' => http_build_query([
                'grant_type' => 'client_credentials',
                'client_id' => $this->sharepointOauthClientId,
                'client_secret' => $this->sharepointOauthClientSecret,
                'resource' => $this->sharepointOauthResource,
            ]),
        ]);

        return $this->extractTokens($response);
    }
}

为了传递所需的参数给授权类型,我们需要在service.yaml中定义服务并绑定参数。

services:
    App\Sharepoint\CustomClientCredentialsGrantType:
        bind:
            $client: '@sharepoint_oauth.client'
            string $sharepointOauthClientId: '%env(SHAREPOINT_OAUTH_CLIENT_ID)%'
            string $sharepointOauthClientSecret: '%env(SHAREPOINT_OAUTH_CLIENT_SECRET)%'
            string $sharepointOauthResource: '%env(SHAREPOINT_OAUTH_RESOURCE)%'
            string $sharepointOauthUrl: '%env(SHAREPOINT_OAUTH_URL)%'

然后,我们需要定义我们的缓存层而不是默认的in-memory,通过在services.yaml中添加以下服务定义来实现。

BenjaminFavre\OAuthHttpClient\TokensCache\SymfonyTokensCacheAdapter:
    bind:
        $cache: '@cache.app'
        $cacheKey: 'sharepoint'

@cache.app是一个应用程序缓存,配置在你的系统中。在我们的例子中,这是一个Redis缓存。

framework:
    cache:
        app: cache.adapter.redis
        default_redis_provider: '%env(REDIS_URL)%'

最后,我们可以在services.yaml中定义我们的OAuthHttpClient服务,该服务使用sharepoint_api.client并设置配置的Redis缓存。

BenjaminFavre\OAuthHttpClient\OAuthHttpClient:
    bind:
        $client: '@sharepoint_api.client'
        $grant: '@App\Sharepoint\CustomClientCredentialsGrantType'
    calls:
        - [ setCache, [ '@BenjaminFavre\OAuthHttpClient\TokensCache\SymfonyTokensCacheAdapter' ] ]

之后,OAuthHttpClient服务就准备好在应用程序的任何其他类中使用。

public function __construct(
    private OAuthHttpClient $sharepointClient,
) {
}