Files
chatroom/app/Http/Middleware/CloudflareProxies.php

107 lines
3.0 KiB
PHP

<?php
/**
* 文件功能:在可信代理场景下解析客户端真实 IP。
*
* 仅当当前请求明确来自配置中的反向代理 / CDN 节点时,
* 才会采信其透传的真实客户端 IP 头,避免外部客户端伪造来源。
*/
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\IpUtils;
use Symfony\Component\HttpFoundation\Response;
/**
* 类功能:为可信代理请求恢复真实客户端 IP。
*/
class CloudflareProxies
{
/**
* 处理进入应用的请求,并在可信代理场景下覆写客户端 IP。
*/
public function handle(Request $request, Closure $next): Response
{
$realIp = $this->resolveTrustedClientIp($request);
if (! empty($realIp)) {
// 仅在确认上游代理可信且透传 IP 合法时,才覆写 request()->ip() 的来源。
$request->server->set('REMOTE_ADDR', $realIp);
$request->headers->set('X-Forwarded-For', $realIp);
}
return $next($request);
}
/**
* 从可信代理头中解析真实客户端 IP。
*/
private function resolveTrustedClientIp(Request $request): ?string
{
$remoteAddress = (string) $request->server->get('REMOTE_ADDR', '');
if (! $this->isTrustedProxy($remoteAddress)) {
return null;
}
foreach (['CF-Connecting-IP', 'EO-Client-IP', 'X-Real-IP'] as $headerName) {
$resolvedIp = $this->sanitizeIp($request->header($headerName));
if ($resolvedIp !== null) {
return $resolvedIp;
}
}
return $this->extractForwardedForIp($request->header('X-Forwarded-For'));
}
/**
* 判断当前请求是否来自受信代理节点。
*/
private function isTrustedProxy(string $remoteAddress): bool
{
if ($this->sanitizeIp($remoteAddress) === null) {
return false;
}
$trustedProxies = config('app.trusted_proxies', ['127.0.0.1', '::1']);
foreach ($trustedProxies as $trustedProxy) {
$trustedProxy = trim((string) $trustedProxy);
if ($trustedProxy !== '' && IpUtils::checkIp($remoteAddress, $trustedProxy)) {
return true;
}
}
return false;
}
/**
* 从 X-Forwarded-For 头中提取最左侧的合法 IP。
*/
private function extractForwardedForIp(?string $forwardedFor): ?string
{
if (! is_string($forwardedFor) || $forwardedFor === '') {
return null;
}
foreach (explode(',', $forwardedFor) as $candidateIp) {
$resolvedIp = $this->sanitizeIp($candidateIp);
if ($resolvedIp !== null) {
return $resolvedIp;
}
}
return null;
}
/**
* 校验并标准化 IP 文本。
*/
private function sanitizeIp(?string $ip): ?string
{
$normalizedIp = trim((string) $ip);
return filter_var($normalizedIp, FILTER_VALIDATE_IP) ? $normalizedIp : null;
}
}