首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP IP 區段存取控制:strcmp 字串比較法與 CIDR 正規解法

     編輯:PHP 綜合知識

原文 strcmp 比較法 (有缺陷)

<?php
$IP = $_SERVER['REMOTE_ADDR'];
$from = strcmp($IP, '192.168.0.0');
$to   = strcmp($IP, '192.168.0.255');

if (!($from >= 0 && $to <= 0)) {
    echo "Access Denied";
} else {
    echo "Homepage";
}

為何 strcmp 比較 IP 不可靠

<?php
// 字串比較 vs 數值比較
var_dump(strcmp('192.168.0.5', '192.168.0.255'));  // int(-1) 正確
var_dump(strcmp('192.168.0.100', '192.168.0.255'));// int(-1) 正確
var_dump(strcmp('192.168.1.5', '192.168.0.255'));  // int(1)  錯誤!1.5 > 0.255 但實際 IP 較大

// 根本原因:字串逐字元比較,'1' > '0' 導致 192.168.1.x 被判定大於 192.168.0.255

正確解法一:ip2long 數值比較 (IPv4 專用)

<?php
function ipInRange($ip, $startIp, $endIp) {
    $ip = ip2long($ip);
    $start = ip2long($startIp);
    $end = ip2long($endIp);
    if ($ip === false || $start === false || $end === false) return false;
    return ($ip >= $start && $ip <= $end);
}

// 使用
if (!ipInRange($_SERVER['REMOTE_ADDR'], '192.168.0.0', '192.168.0.255')) {
    header('HTTP/1.1 403 Forbidden');
    exit('Access Denied');
}

正確解法二:CIDR 表示法 (推薦、支援 IPv6)

<?php
function ipInCidr($ip, $cidr) {
    list($subnet, $mask) = explode('/', $cidr);
    $mask = $mask ?: 32;
    
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
        $ip = ip2long($ip);
        $subnet = ip2long($subnet);
        $mask = ~((1 << (32 - $mask)) - 1);
        return ($ip & $mask) === ($subnet & $mask);
    }
    
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
        // IPv6 需用 inet_pton 二進位比較
        $ip = inet_pton($ip);
        $subnet = inet_pton($subnet);
        $mask = str_repeat('f', $mask / 4) . str_repeat('0', 32 - $mask / 4);
        // 簡化:建議直接用 套件
        return false; // 此處略
    }
    return false;
}

// 使用:192.168.0.0/24 代表 192.168.0.0 - 192.168.0.255
if (!ipInCidr($_SERVER['REMOTE_ADDR'], '192.168.0.0/24')) {
    exit('Access Denied');
}

正確解法三:使用成熟套件 (生產環境首選)

composer require ramsey/ip

use Ramsey\Ip\IpRange;

$range = IpRange::fromCidr('192.168.0.0/24');
if (!$range->contains($_SERVER['REMOTE_ADDR'])) {
    http_response_code(403);
    exit('Access Denied');
}

完整存取控制中介層範例

<?php
class IpAccessControl {
    private $allowList = [];
    private $denyList = [];
    
    public function __construct(array $config) {
        $this->allowList = $config['allow'] ?? [];
        $this->denyList = $config['deny'] ?? [];
    }
    
    public function check() {
        $ip = $this->getClientIp();
        
        // 拒絕名單優先
        foreach ($this->denyList as $cidr) {
            if ($this->match($ip, $cidr)) return false;
        }
        // 允許名單為空 = 全部允許 (除非被拒絕)
        if (empty($this->allowList)) return true;
        
        foreach ($this->allowList as $cidr) {
            if ($this->match($ip, $cidr)) return true;
        }
        return false;
    }
    
    private function getClientIp() {
        // 同前文:支援 X-Forwarded-For、真實 IP 取得
        $headers = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
        foreach ($headers as $h) {
            if (!empty($_SERVER[$h]) && $_SERVER[$h] !== 'unknown') {
                $ips = explode(',', $_SERVER[$h]);
                return trim($ips[0]);
            }
        }
        return '0.0.0.0';
    }
    
    private function match($ip, $cidr) {
        // 使用 ramsey/ip 或自行實作 CIDR 比對
        return IpRange::fromCidr($cidr)->contains($ip);
    }
}

// 使用
$config = [
    'allow' => ['192.168.0.0/24', '10.0.0.0/8'],
    'deny'  => ['192.168.0.100/32'], // 具體 IP 封鎖
];
$guard = new IpAccessControl($config);
if (!$guard->check()) {
    http_response_code(403);
    include '403.php';
    exit;
}

跳轉 vs 直接輸出 vs 包含檔案

方式程式碼適用場景
直接輸出echo "Access Denied";API、AJAX 端點
包含頁面include 'homepage.html';傳統 PHP 頁面、SEO 需求
HTTP 跳轉header('Location: /403.html');統一錯誤頁、記錄追蹤
JSON 回應header('Content-Type: application/json'); echo json_encode(['error'=>'forbidden']);REST API
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved