rysonliu / http-signature
IETF HTTP Signatures 草案 RFC 的实现
Requires
- php: >=7.2.0
- improved/improved: ^0.1.0
- nesbot/carbon: ^2.12
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
Requires (Dev)
- guzzlehttp/guzzle: ^6.3
- jasny/php-code-quality: 2.4.*
- php-http/client-common: ^2.0
- php-http/mock-client: ^1.3
This package is auto-updated.
Last update: 2024-09-17 08:58:57 UTC
README
这个库提供了一个实现IETF HTTP Signatures 草案 RFC的服务的功能。它包括与 PSR-7 兼容的中间件,用于签名请求(通过像 Guzzle 这样的 HTTP 客户端)和验证 http 签名。
安装
composer require rysonliu/http-signature
用法
创建 HttpSignature
服务时,传递一个支持的算法列表、一个签名请求的回调和一个验证签名的回调。
use Jasny\HttpSignature\HttpSignature; $keys = [ 'hmac-key-1' => 'secret', 'hmac-key-2' => 'god', ]; $service = new HttpSignature( 'hmac-sha256', function (string $message, string $keyId) use ($keys): string { if (!isset($keys[$keyId])) { throw new OutOfBoundsException("Unknown sign key '$keyId'"); } $key = $keys[$keyId]; return hash_hmac('sha256', $message, $key, true); }, function (string $message, string $signature, string $keyId) use ($keys): bool { if (!isset($keys[$keyId])) { return false; } $key = $keys[$keyId]; $expected = hash_hmac('sha256', $message, $key, true); return hash_equals($expected, $signature); } );
签名请求
您可以使用该服务对 PSR-7 请求进行签名。
$request = new Request(); // Any PSR-7 compatible Request object $signedRequest = $service->sign($request, $keyId);
验证请求
您可以使用该服务验证已签名的 PSR-7 请求的签名。
$request = new Request(); // Any PSR-7 compatible Request object $service->verify($request);
如果请求未签名、签名无效或请求不符合要求,将抛出 HttpSignatureException
。
配置服务
多个算法
而不是指定单个算法,可以在构造函数中指定一个支持算法的数组。使用的算法作为额外的参数传递给签名和验证回调。
use Jasny\HttpSignature\HttpSignature; $service = new HttpSignature( ['hmac-sha256', 'rsa', 'rsa-sha256'], function (string $message, string $keyId, string $algorithm): string { // ... }, function (string $message, string $signature, string $keyId, string $algorithm): bool { // ... } );
签名时指定算法;
$signedRequest = $service->sign($request, $keyId, 'hmac-sha256');
或者您可以选择具有所选算法的服务副本。
$signService = $service->withAlgorithm('hmac-sha256'); $signService->sign($request, $keyId);
必需的头部信息
默认情况下,请求目标(包括 HTTP 方法、URL 路径和查询参数)和 Date
头部对于所有类型的请求都需要作为签名消息的一部分。
$service = $service->withRequiredHeaders('POST', ['(request-target)', 'date', 'content-type', 'digest']);
必需的头部信息可以按请求方法或作为 default
指定。
请注意,要求仅适用于将头部信息包含在创建签名的过程中。如果头部信息未用于请求,则它们也不是签名的一部分。检查请求中是否设置了头部信息以及是否有有效的值,超出了此库的范围。
Date
头部
如果指定了 Date
头部,服务将检查请求的年龄。如果它签名的太早,将抛出异常。默认情况下,请求不能超过 300 秒(5 分钟)。
请求签名和验证之间的时间可能由于延迟或客户端和/或服务器的系统时钟可能不正确。
允许的时间可以配置为时钟偏移;
$service = $service->withClockSkew(1800); // Accept requests up to 30 minutes old
X-Date
头部
浏览器会自动设置 AJAX 请求的 Date
头部。这使得无法将其用于签名。作为解决方案,可以使用替代的 X-Date
头部。
服务器中间件
可以使用服务器中间件来验证 PSR-7 请求。
如果请求已签名但签名无效,中间件将返回 401 Unauthorized
响应,并且处理程序将不会调用。
单次传递中间件(PSR-15)
中间件实现了 PSR-15 MiddlewareInterface
。作为 PSR 标准,许多新的库支持此类中间件,例如 Zend Stratigility。
您需要提供一个 PSR-17 响应工厂,用于为具有无效签名的请求创建 401 Unauthorized
响应。
use Jasny\HttpSignature\HttpSignature; use Jasny\HttpSignature\ServerMiddleware; use Zend\Stratigility\MiddlewarePipe; use Zend\Diactoros\ResponseFactory; $service = new HttpSignature(/* ... */); $responseFactory = new ResponseFactory(); $middleware = new ServerMiddleware($service, $responseFactory); $app = new MiddlewarePipe(); $app->pipe($middleware);
双重传递中间件
我的 PHP 库支持双重传递中间件。这些是具有以下签名的调用:
fn(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
为了使库如 Jasny Router 和 Relay 使用回调,请使用 asDoublePass()
方法。
当用作双遍历中间件时,提供资源工厂是可选的。如果没有提供,它将使用调用时传递的响应。
use Jasny\HttpSignature\HttpSignature; use Jasny\HttpSignature\ServerMiddleware; use Relay\RelayBuilder; $service = new HttpSignature(/* ... */); $middleware = new ServerMiddleware($service); $relayBuilder = new RelayBuilder($resolver); $relay = $relayBuilder->newInstance([ $middleware->asDoublePass(), ]); $response = $relay($request, $baseResponse);
验证请求
如果请求已签名且签名有效,中间件将设置 signature_key_id
请求属性。
对于未签名的请求,中间件不执行任何操作。这意味着您需要始终检查请求是否具有 signature_key_id
。
$keyId = $request->getAttribute(`signature_key_id`); if ($keyId === null) { $errorResponse = $response ->withStatus(401) ->withHeader('Content-Type', 'text/plain'); $errorResponse = $service->setAuthenticateResponseHeader($errorResponse); $errorResponse->getBody()->write('request not signed'); } // Request is signed and signature is valid // ...
客户端中间件
客户端中间件可用于对 PSR-7 兼容的 HTTP 客户端(如 Guzzle 和 HTTPlug)发送的请求进行签名。
use Jasny\HttpSignature\HttpSignature; use Jasny\HttpSignature\ClientMiddleware; $service = new HttpSignature(/* ... */); $middleware = new ClientMiddleware($service, $keyId);
$keyId
用于 Authorization
标头并传递给签名回调。
如果服务支持多种算法,您需要使用 withAlgorithm
方法选择一个。
$middleware = new ClientMiddleware($service->withAlgorithm('hmac-sha256'));
双重传递中间件
客户端中间件可用于任何支持双遍历中间件的客户端。此类中间件是具有以下签名的调用者;
fn(RequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface
大多数 HTTP 客户端不支持双遍历中间件,而是使用单遍历。但是,像 Relay 这样的更通用目的的 PSR-7 中间件库则支持双遍历。
use Relay\RelayBuilder; $relayBuilder = new RelayBuilder($resolver); $relay = $relayBuilder->newInstance([ $middleware->asDoublePass(), ]); $response = $relay($request, $baseResponse);
客户端中间件不符合 PSR-15(单遍历),因为它是为服务器请求而设计的。
Guzzle
Guzzle 是最流行的 PHP HTTP 客户端。中间件有一个 forGuzzle()
方法,该方法创建一个回调,可以作为 Guzzle 中间件使用。
当为 Guzzle 使用中间件时,不需要将 $keyId
传递给构造函数。相反,使用 Guzzle 选项 signature_key_id
。这也允许使用不同的密钥进行每个请求或禁用请求的签名。
use GuzzleHttp\HandlerStack; use GuzzleHttp\Client; use Jasny\HttpSignature\HttpSignature; use Jasny\HttpSignature\ClientMiddleware; $service = new HttpSignature(/* ... */); $middleware = new ClientMiddleware($service); $stack = new HandlerStack(); $stack->push($middleware->forGuzzle()); $client = new Client(['handler' => $stack, 'signature_key_id' => $keyId]); $client->get('/foo'); // Sign with default key $client->get('/foo', ['signature_key_id' => $altKeyId]); // Sign with other key $client->get('/foo', ['signature_key_id' => null]); // Don't sign
或者,您可以默认禁用签名,仅在指定时进行签名;
$client = new Client(['handler' => $stack]); $client->get('/foo'); // Don't sign $client->get('/foo', ['signature_key_id' => $keyId]); // Sign
选项仅适用于 Guzzle。对于 HTTPlug 和其他客户端,您需要为每个密钥创建一个客户端或无中间件进行签名。
HTTPlug
HTTPlug 是 PHP-HTTP 的 HTTP 客户端。它允许您编写需要 HTTP 客户端但不绑定到特定实现的可重用库和应用程序。
中间件的 forHttplug()
方法创建一个可以用作 HTTPlug 插件的对象。
use Http\Discovery\HttpClientDiscovery; use Http\Client\Common\PluginClient; use Jasny\HttpSignature\HttpSignature; use Jasny\HttpSignature\ClientMiddleware; $service = new HttpSignature(/* ... */); $middleware = new ClientMiddleware($service, $keyId); $pluginClient = new PluginClient( HttpClientDiscovery::find(), [ $middleware->forHttplug(), ] );