IpWhitelistValidator.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <?php
  2. namespace App\Module\OpenAPI\Validators;
  3. use UCore\Validator;
  4. /**
  5. * IP白名单验证器
  6. */
  7. class IpWhitelistValidator extends Validator
  8. {
  9. /**
  10. * 验证IP白名单配置是否有效
  11. *
  12. * @param mixed $value IP白名单数组
  13. * @param array $data 验证数据
  14. * @return bool 验证是否通过
  15. */
  16. public function validate(mixed $value, array $data): bool
  17. {
  18. // 从 args 获取参数
  19. $processedKey = $this->args[0] ?? 'processedIpWhitelist';
  20. // 如果为空,表示不限制IP
  21. if (empty($value)) {
  22. $this->validation->$processedKey = null;
  23. return true;
  24. }
  25. if (!is_array($value)) {
  26. $this->addError('IP白名单必须是数组格式');
  27. return false;
  28. }
  29. // 验证IP数量
  30. if (count($value) > 100) {
  31. $this->addError('IP白名单数量不能超过100个');
  32. return false;
  33. }
  34. $processedIps = [];
  35. foreach ($value as $index => $ip) {
  36. if (!is_string($ip)) {
  37. $this->addError("第" . ($index + 1) . "个IP地址必须是字符串");
  38. return false;
  39. }
  40. $ip = trim($ip);
  41. if (empty($ip)) {
  42. continue;
  43. }
  44. // 验证IP格式
  45. if (!$this->validateIpFormat($ip, $index + 1)) {
  46. return false;
  47. }
  48. $processedIps[] = $ip;
  49. }
  50. // 去重
  51. $processedIps = array_unique($processedIps);
  52. if (empty($processedIps)) {
  53. $this->validation->$processedKey = null;
  54. return true;
  55. }
  56. // 验证IP白名单的安全性
  57. if (!$this->validateIpSecurity($processedIps)) {
  58. return false;
  59. }
  60. // 将处理后的IP白名单保存到验证对象中
  61. $this->validation->$processedKey = array_values($processedIps);
  62. return true;
  63. }
  64. /**
  65. * 验证IP格式
  66. *
  67. * @param string $ip
  68. * @param int $index
  69. * @return bool
  70. */
  71. protected function validateIpFormat(string $ip, int $index): bool
  72. {
  73. // 支持CIDR格式
  74. if (str_contains($ip, '/')) {
  75. return $this->validateCidrFormat($ip, $index);
  76. }
  77. // 支持通配符格式
  78. if (str_contains($ip, '*')) {
  79. return $this->validateWildcardFormat($ip, $index);
  80. }
  81. // 普通IP地址
  82. if (!filter_var($ip, FILTER_VALIDATE_IP)) {
  83. $this->addError("第{$index}个IP地址格式错误: {$ip}");
  84. return false;
  85. }
  86. return true;
  87. }
  88. /**
  89. * 验证CIDR格式
  90. *
  91. * @param string $cidr
  92. * @param int $index
  93. * @return bool
  94. */
  95. protected function validateCidrFormat(string $cidr, int $index): bool
  96. {
  97. $parts = explode('/', $cidr);
  98. if (count($parts) !== 2) {
  99. $this->addError("第{$index}个CIDR格式错误: {$cidr}");
  100. return false;
  101. }
  102. [$ip, $mask] = $parts;
  103. // 验证IP地址
  104. if (!filter_var($ip, FILTER_VALIDATE_IP)) {
  105. $this->addError("第{$index}个CIDR中的IP地址格式错误: {$cidr}");
  106. return false;
  107. }
  108. // 验证子网掩码
  109. if (!is_numeric($mask)) {
  110. $this->addError("第{$index}个CIDR中的子网掩码必须是数字: {$cidr}");
  111. return false;
  112. }
  113. $mask = (int)$mask;
  114. if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
  115. // IPv4
  116. if ($mask < 0 || $mask > 32) {
  117. $this->addError("第{$index}个IPv4 CIDR的子网掩码范围应为0-32: {$cidr}");
  118. return false;
  119. }
  120. } else {
  121. // IPv6
  122. if ($mask < 0 || $mask > 128) {
  123. $this->addError("第{$index}个IPv6 CIDR的子网掩码范围应为0-128: {$cidr}");
  124. return false;
  125. }
  126. }
  127. return true;
  128. }
  129. /**
  130. * 验证通配符格式
  131. *
  132. * @param string $ip
  133. * @param int $index
  134. * @return bool
  135. */
  136. protected function validateWildcardFormat(string $ip, int $index): bool
  137. {
  138. // 将*替换为0进行验证
  139. $testIp = str_replace('*', '0', $ip);
  140. if (!filter_var($testIp, FILTER_VALIDATE_IP)) {
  141. $this->addError("第{$index}个通配符IP格式错误: {$ip}");
  142. return false;
  143. }
  144. // 验证通配符位置的合理性
  145. if (filter_var($testIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
  146. // IPv4通配符验证
  147. $parts = explode('.', $ip);
  148. $wildcardFound = false;
  149. foreach ($parts as $part) {
  150. if ($part === '*') {
  151. $wildcardFound = true;
  152. } elseif ($wildcardFound && $part !== '*') {
  153. $this->addError("第{$index}个IPv4通配符格式错误,通配符后面的段也必须是通配符: {$ip}");
  154. return false;
  155. }
  156. }
  157. }
  158. return true;
  159. }
  160. /**
  161. * 验证IP白名单的安全性
  162. *
  163. * @param array $ips
  164. * @return bool
  165. */
  166. protected function validateIpSecurity(array $ips): bool
  167. {
  168. foreach ($ips as $ip) {
  169. // 检查是否包含过于宽泛的IP范围
  170. if ($this->isTooPermissive($ip)) {
  171. $this->addWarning("IP地址 {$ip} 范围过于宽泛,可能存在安全风险");
  172. }
  173. // 检查是否包含内网IP
  174. if ($this->isPrivateIp($ip)) {
  175. $this->addWarning("IP地址 {$ip} 是内网地址,请确认是否正确");
  176. }
  177. }
  178. return true;
  179. }
  180. /**
  181. * 检查IP范围是否过于宽泛
  182. *
  183. * @param string $ip
  184. * @return bool
  185. */
  186. protected function isTooPermissive(string $ip): bool
  187. {
  188. // 检查CIDR
  189. if (str_contains($ip, '/')) {
  190. [$ipAddr, $mask] = explode('/', $ip);
  191. $mask = (int)$mask;
  192. if (filter_var($ipAddr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
  193. // IPv4: 小于/16的网段认为过于宽泛
  194. return $mask < 16;
  195. } else {
  196. // IPv6: 小于/64的网段认为过于宽泛
  197. return $mask < 64;
  198. }
  199. }
  200. // 检查通配符
  201. if (str_contains($ip, '*')) {
  202. $wildcardCount = substr_count($ip, '*');
  203. // 超过2个通配符认为过于宽泛
  204. return $wildcardCount > 2;
  205. }
  206. return false;
  207. }
  208. /**
  209. * 检查是否是内网IP
  210. *
  211. * @param string $ip
  212. * @return bool
  213. */
  214. protected function isPrivateIp(string $ip): bool
  215. {
  216. // 提取IP地址(去除CIDR掩码和通配符)
  217. $testIp = explode('/', $ip)[0];
  218. $testIp = str_replace('*', '1', $testIp);
  219. if (!filter_var($testIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
  220. return false;
  221. }
  222. // 检查是否是私有IP范围
  223. return filter_var($testIp, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE) === false;
  224. }
  225. }