args[0] ?? 'processedIpWhitelist'; // 如果为空,表示不限制IP if (empty($value)) { $this->validation->$processedKey = null; return true; } if (!is_array($value)) { $this->addError('IP白名单必须是数组格式'); return false; } // 验证IP数量 if (count($value) > 100) { $this->addError('IP白名单数量不能超过100个'); return false; } $processedIps = []; foreach ($value as $index => $ip) { if (!is_string($ip)) { $this->addError("第" . ($index + 1) . "个IP地址必须是字符串"); return false; } $ip = trim($ip); if (empty($ip)) { continue; } // 验证IP格式 if (!$this->validateIpFormat($ip, $index + 1)) { return false; } $processedIps[] = $ip; } // 去重 $processedIps = array_unique($processedIps); if (empty($processedIps)) { $this->validation->$processedKey = null; return true; } // 验证IP白名单的安全性 if (!$this->validateIpSecurity($processedIps)) { return false; } // 将处理后的IP白名单保存到验证对象中 $this->validation->$processedKey = array_values($processedIps); return true; } /** * 验证IP格式 * * @param string $ip * @param int $index * @return bool */ protected function validateIpFormat(string $ip, int $index): bool { // 支持CIDR格式 if (str_contains($ip, '/')) { return $this->validateCidrFormat($ip, $index); } // 支持通配符格式 if (str_contains($ip, '*')) { return $this->validateWildcardFormat($ip, $index); } // 普通IP地址 if (!filter_var($ip, FILTER_VALIDATE_IP)) { $this->addError("第{$index}个IP地址格式错误: {$ip}"); return false; } return true; } /** * 验证CIDR格式 * * @param string $cidr * @param int $index * @return bool */ protected function validateCidrFormat(string $cidr, int $index): bool { $parts = explode('/', $cidr); if (count($parts) !== 2) { $this->addError("第{$index}个CIDR格式错误: {$cidr}"); return false; } [$ip, $mask] = $parts; // 验证IP地址 if (!filter_var($ip, FILTER_VALIDATE_IP)) { $this->addError("第{$index}个CIDR中的IP地址格式错误: {$cidr}"); return false; } // 验证子网掩码 if (!is_numeric($mask)) { $this->addError("第{$index}个CIDR中的子网掩码必须是数字: {$cidr}"); return false; } $mask = (int)$mask; if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { // IPv4 if ($mask < 0 || $mask > 32) { $this->addError("第{$index}个IPv4 CIDR的子网掩码范围应为0-32: {$cidr}"); return false; } } else { // IPv6 if ($mask < 0 || $mask > 128) { $this->addError("第{$index}个IPv6 CIDR的子网掩码范围应为0-128: {$cidr}"); return false; } } return true; } /** * 验证通配符格式 * * @param string $ip * @param int $index * @return bool */ protected function validateWildcardFormat(string $ip, int $index): bool { // 将*替换为0进行验证 $testIp = str_replace('*', '0', $ip); if (!filter_var($testIp, FILTER_VALIDATE_IP)) { $this->addError("第{$index}个通配符IP格式错误: {$ip}"); return false; } // 验证通配符位置的合理性 if (filter_var($testIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { // IPv4通配符验证 $parts = explode('.', $ip); $wildcardFound = false; foreach ($parts as $part) { if ($part === '*') { $wildcardFound = true; } elseif ($wildcardFound && $part !== '*') { $this->addError("第{$index}个IPv4通配符格式错误,通配符后面的段也必须是通配符: {$ip}"); return false; } } } return true; } /** * 验证IP白名单的安全性 * * @param array $ips * @return bool */ protected function validateIpSecurity(array $ips): bool { foreach ($ips as $ip) { // 检查是否包含过于宽泛的IP范围 if ($this->isTooPermissive($ip)) { $this->addWarning("IP地址 {$ip} 范围过于宽泛,可能存在安全风险"); } // 检查是否包含内网IP if ($this->isPrivateIp($ip)) { $this->addWarning("IP地址 {$ip} 是内网地址,请确认是否正确"); } } return true; } /** * 检查IP范围是否过于宽泛 * * @param string $ip * @return bool */ protected function isTooPermissive(string $ip): bool { // 检查CIDR if (str_contains($ip, '/')) { [$ipAddr, $mask] = explode('/', $ip); $mask = (int)$mask; if (filter_var($ipAddr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { // IPv4: 小于/16的网段认为过于宽泛 return $mask < 16; } else { // IPv6: 小于/64的网段认为过于宽泛 return $mask < 64; } } // 检查通配符 if (str_contains($ip, '*')) { $wildcardCount = substr_count($ip, '*'); // 超过2个通配符认为过于宽泛 return $wildcardCount > 2; } return false; } /** * 检查是否是内网IP * * @param string $ip * @return bool */ protected function isPrivateIp(string $ip): bool { // 提取IP地址(去除CIDR掩码和通配符) $testIp = explode('/', $ip)[0]; $testIp = str_replace('*', '1', $testIp); if (!filter_var($testIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return false; } // 检查是否是私有IP范围 return filter_var($testIp, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false; } }