| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- <?php
- namespace App\Module\OpenAPI\Validators;
- use UCore\Validator;
- /**
- * IP白名单验证器
- */
- class IpWhitelistValidator extends Validator
- {
- /**
- * 验证IP白名单配置是否有效
- *
- * @param mixed $value IP白名单数组
- * @param array $data 验证数据
- * @return bool 验证是否通过
- */
- public function validate(mixed $value, array $data): bool
- {
- // 从 args 获取参数
- $processedKey = $this->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;
- }
- }
|