jasny/forwarded-middleware

处理PSR-7请求的Forwarded头部的服务器中间件

v0.1.0 2019-10-24 11:08 UTC

This package is auto-updated.

Last update: 2024-08-29 05:18:36 UTC


README

Build Status Scrutinizer Code Quality Code Coverage Packagist Stable Version Packagist License

服务器中间件用于处理PSR-7请求的Forwarded头部。同时作为PSR-15和双次传递中间件使用。

中间件设置服务器请求的client_iporiginal_url属性。还支持非标准的X-Forwarded-*和其他自定义头部。

安装

composer require jasny/forwarded-middleware

用法

use Wikimedia\IPSet;
use Jasny\Forwarded;
use Zend\Stratigility\MiddlewarePipe;
use Zend\Diactoros\ResponseFactory;

$trustIps = new IPSet(['208.80.154.0/26', '2620:0:861:1::/64', '10.64.0.0/22']);

$middleware = new Forwarded\Middleware(function (string $ip, array $forward) use ($trustedIps) {
    return $trustedIps->match($ip);
});

$app = new MiddlewarePipe();
$app->pipe($middleware);

受信任的代理

构造函数仅接受一个回调参数,该回调用于每个在Forwarded头部中指定的转发(由逗号分隔)。每个转发应有一个for指令,并可能包含其他指令,如portproto

回调的第一个参数是代理服务器的IP。第二个参数包含此转发的指令,作为关联数组。

IP的初始值来自REMOTE_ADDR服务器参数。对于后续的每次调用,都给出前一个受信任转发的for指令的值。

示例

Forwarded: for=75.84.3.2,for=92.53.34.1,for=32.5.86.102,for=10.64.0.23
Client IP (REMOTE_ADDR) is 208.80.154.8 

这将导致以下调用

fn("208.80.154.8", ['for' => "10.64.0.23"]);  // true
fn("10.64.0.23", ['for' => "32.5.86.102"]);   // true
fn("32.5.86.102", ['for' => "92.53.34.1"]);   // false

client_id设置为"32.5.86.102"。请注意,for=75.84.3.2的转发不被考虑。

不一定要基于IP信任。作为替代,您可以检查代理是否设置了秘密。

$middleware = new Forwarded\Middleware(function (string $ip, array $forward) {
    return $forward['secret'] === getenv('PROXY_SECRET');
});

原始uri

除了client_ip之外,中间件还会设置original_uri属性。此属性是基于请求URI的PSR-7 URI对象。

protohost,以及非标准的pathport指令被应用于创建原始uri。如果portproto的标准端口(对于"http"是80,对于"https"是443),则省略。

仅使用最后一个受信任代理的指令;

示例

HTTP/1.1 GET /foo
Host: x9.example.com
Forwarded: for=92.53.34.1, for=32.5.86.102;proto=https;port=443;host=example.com;path=/x/foo, for=10.64.0.23;proto=http;port=8080

original_uri属性将是"https://example.com/x/foo"

服务器请求的URI不会被修改。

非标准头部

如果您的代理设置了X-Forwarded-*头部,请使用CompatMiddleware。此中间件将这些头部转换为Forwarded头部。

use Wikimedia\IPSet;
use Jasny\Forwarded;
use Zend\Stratigility\MiddlewarePipe;
use Zend\Diactoros\ResponseFactory;

$trustIps = new IPSet(['208.80.154.0/26', '2620:0:861:1::/64', '10.64.0.0/22']);

$compatMiddleware = new Forwarded\CompatMiddleware();
$middleware = new Forwarded\Middleware(function (string $ip, array $forward) use ($trustedIps) {
    return $trustedIps->match($ip);
});

$app = new MiddlewarePipe();
$app->pipe($compatMiddleware);
$app->pipe($middleware);

默认情况下,兼容中间件使用以下头部

  • X-Forwared-For
  • X-Forwarded-Proto
  • X-Forwarded-Host

自定义头部

如果您的代理使用其他头部,您可以传递自定义映射给构造函数

$compatMiddleware = new Forwarded\CompatMiddleware([
    'X-Client-IP' => 'for,
    'X-Forwarded-For' => 'for',
    'X-Forwarded-Proto' => 'proto',
    'X-Forwarded-Port' => 'port',
]);

如果有多个头部具有相同的指令,则使用找到的第一个头部。在上面的例子中,如果有X-Client-IP,则不使用X-Forwarded-For头部。

兼容中间件支持将任何映射到for指令的头部使用多个条目。其他指令应用于第一个条目。如果X-Forwarded-For包含受信任和不信任代理的条目,这可能不会按预期工作。

兼容中间件将始终替换或删除现有的Forwarded头部。通常,代理要么设置Forwarded头部,要么使用非标准头部。允许两者都可能导致安全问题。

双次传递中间件

某些PHP库支持双次传递中间件而不是PSR-15,并使用以下签名的可调用对象;

fn(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface

要获取用于Jasny RouterRelay v1的回调,请使用asDoublePass()方法。

use Jasny\Forwarded;
use Relay\RelayBuilder;

$middleware = new Forwarded\Middleware(function ($ip, $forward) { /* ... */ });

$relayBuilder = new RelayBuilder($resolver);
$relay = $relayBuilder->newInstance([
    $middleware->asDoublePass(),
]);

$response = $relay($request, $baseResponse);

CompatMiddleware 还有一个 asDoublePass() 方法。