HOWTO · PHP
PHP에서 사용자 IP 주소 얻기
이 튜토리얼은 PHP에서 사용자의 IP 주소를 얻는 방법을 보여줍니다.
이 페이지의 내용
PHP 요청과 연결된 네트워크 주소를 확인하려면 먼저 $_SERVER['REMOTE_ADDR']를 확인합니다. 이 값은 웹 서버에 연결된 피어의 주소입니다. 애플리케이션 앞에 리버스 프록시가 있으면 이 피어는 방문자가 아니라 프록시일 수 있습니다. 프록시 관련 요청 헤더는 해당 헤더를 제공하고 검증하는 프록시에 대해 애플리케이션에 명시적인 신뢰 설정이 있을 때만 사용할 수 있습니다.
$_SERVER['REMOTE_ADDR']로 연결 주소 읽기
$_SERVER는 서버와 요청 정보를 담는 PHP 슈퍼글로벌입니다. 그중 REMOTE_ADDR 요소에는 요청을 처리하는 서버에 연결된 피어의 주소가 들어 있습니다. 직접 연결에서는 보통 연결한 클라이언트의 주소입니다. 로드 밸런서나 리버스 프록시 뒤에서는 대개 프록시의 주소입니다. PHP가 받는 값은 웹 서버와 프록시 설정에 따라 달라지므로, 이 값을 최종 사용자 주소라고 단정해서는 안 됩니다.
로컬 요청에서 피어 주소는 IPv6 루프백 주소인 ::1일 수 있습니다. 루프백 주소는 로컬 컴퓨터를 가리킵니다. ::1은 IPv6 루프백 주소이며 IPv4의 127.0.0.1에 해당합니다. 정확한 값은 로컬 서버와 연결 방식에 따라 달라집니다. 자세한 내용은 루프백 주소 설명을 참고하세요.
다음 실행 가능한 예제는 조회를 독립적으로 보여 주기 위해 대표적인 서버 값을 설정합니다. display_output을 false로 두므로 조회를 수행해도 화면에 출력하지 않습니다. 애플리케이션에서는 응답이나 로깅 목적에 맞게 값을 사용하세요.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
<?php
$_SERVER['REMOTE_ADDR'] = '::1';
$peerAddress = $_SERVER['REMOTE_ADDR'];
$display_output = false;
if ($display_output) {
echo "The connected peer is: " . $peerAddress . PHP_EOL;
}
?>
실제 요청에서는 예시 값을 직접 지정하는 대신 서버가 제공하는 $_SERVER['REMOTE_ADDR'] 값을 사용하세요. PHP 매뉴얼의 서버 변수 참조에서 사용할 수 있는 $_SERVER 항목을 설명합니다.
신뢰하는 프록시일 때만 프록시 헤더 해석하기
PHP는 $_SERVER 항목으로 HTTP_X_FORWARDED_FOR나 HTTP_CLIENT_IP 같은 요청 헤더를 제공할 수 있습니다. 헤더가 존재한다고 해서 그 값이 방문자의 주소라는 뜻은 아닙니다. 클라이언트가 임의의 헤더 값을 보낼 수 있고, 프록시는 그 값을 유지하거나 덧붙이거나 바꿀 수 있습니다. 특히 HTTP_X_FORWARDED_FOR에는 주소 하나가 아니라 쉼표로 구분된 주소 목록이 들어갈 수 있습니다. 인프라에 문서화된 신뢰 규칙을 적용하지 않은 채 첫 번째 값을 실제 클라이언트 주소라고 간주하지 마세요.
연결 피어가 애플리케이션에서 명시적으로 신뢰하는 프록시와 일치하고, 그 프록시가 클라이언트가 보낸 전달 헤더를 제거하거나 검증하도록 설정된 경우에만 전달된 값을 사용하세요. 신뢰할 프록시 목록은 실제 배포 환경에 맞아야 합니다. 예시 주소를 프로덕션 설정에 그대로 복사하지 마세요. 피어를 신뢰할 수 없다면 REMOTE_ADDR을 사용하세요. HTTP_CLIENT_IP에도 같은 신뢰 문제가 있으므로, 공유 인터넷 연결에서 특별한 정답처럼 사용해서는 안 됩니다.
이 실행 가능한 코드 조각은 신뢰하는 프록시가 X-Forwarded-For를 검증된 주소 하나로 덮어쓰는 배포 환경을 모델링합니다. 예시 프록시 주소와 검증 정책을 실제 인프라에서 정한 값으로 바꾸세요. 대표적인 값에 대해 선택된 전달 주소를 출력하며, 여러 프록시를 거치는 주소 목록의 파싱은 구현하지 않습니다. 출력 블록에 고정된 영어 라벨이 있다면 바이트 보존을 위해 영어 그대로 남습니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
<?php
$_SERVER['REMOTE_ADDR'] = '192.0.2.10';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '198.51.100.24';
$trustedProxies = ['192.0.2.10'];
$peerAddress = $_SERVER['REMOTE_ADDR'];
if (in_array($peerAddress, $trustedProxies, true)
&& isset($_SERVER['HTTP_X_FORWARDED_FOR'])
&& filter_var($_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP) !== false) {
$clientAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$clientAddress = $peerAddress;
}
echo "Selected address: " . $clientAddress . PHP_EOL;
?>
Selected address: 198.51.100.24
filter_var() 문서에서 여기서 사용하는 검증 함수를 설명합니다. 문자열이 IP 주소인지 검증해도 그 값이 신뢰할 수 있는 출처에서 왔다는 사실까지 확인되는 것은 아닙니다. 그 신뢰 여부는 피어 검사와 프록시 설정으로 판단해야 합니다. 프록시가 주소 목록을 전달한다면 실제 배포 환경의 구체적인 신뢰 규칙에 따라 목록을 순회하는 프레임워크나 신중하게 검토된 코드를 사용하세요.
간결한 대체 로직에 조건식을 사용하기
읽기 쉬운 if/elseif/else 블록은 보통 검토하기 쉽습니다. 특히 프록시 신뢰 검사까지 포함한다면 더 그렇습니다. 조건식은 같은 선택을 간결하게 표현할 수 있지만, 신뢰할 수 없는 헤더를 안전하게 만들어 주지는 않습니다. 다음 실행 가능한 예제는 위에서 설명한 것과 같은 신뢰 프록시 검사와 단일 주소 덮어쓰기 정책을 사용합니다. 서버 값은 예시이며, 선택된 전달 주소를 출력합니다. 출력 블록에 고정된 영어 라벨이 있다면 바이트 보존을 위해 영어 그대로 남습니다.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
<?php
$_SERVER['REMOTE_ADDR'] = '192.0.2.10';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '198.51.100.24';
$trustedProxies = ['192.0.2.10'];
$peerAddress = $_SERVER['REMOTE_ADDR'];
$forwardedAddress = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
$clientAddress = in_array($peerAddress, $trustedProxies, true)
&& filter_var($forwardedAddress, FILTER_VALIDATE_IP) !== false
? $forwardedAddress
: $peerAddress;
echo "Selected address: " . $clientAddress . PHP_EOL;
?>
Selected address: 198.51.100.24
대체 순서는 의도적으로 정해져 있습니다. 연결된 피어가 신뢰 프록시 목록에 있을 때만 검증된 전달 주소를 사용하고, 그렇지 않으면 연결된 피어 주소를 유지합니다. 괄호를 사용하면 중첩 조건식을 더 명확하게 만들 수 있지만, 중첩 삼항 연산자는 유지보수가 더 어렵고 신뢰 검사를 대신하지 않습니다. 클라이언트가 자신의 헤더를 신뢰된 입력으로 승격하지 못하도록 프록시의 헤더 처리 방식과 애플리케이션의 허용 목록을 일치시키세요.