ApiValidator.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. namespace App\Module\OpenAPI\Validators;
  3. use UCore\Validator;
  4. /**
  5. * API验证器
  6. *
  7. * 用于验证API相关的数据
  8. */
  9. class ApiValidator extends Validator
  10. {
  11. /**
  12. * 验证API密钥格式
  13. *
  14. * @param string $apiKey
  15. * @param string $arg
  16. * @return void
  17. */
  18. public function validateApiKey(string $apiKey, string $arg): void
  19. {
  20. if (empty($apiKey)) {
  21. $this->addError($arg, 'API密钥不能为空');
  22. return;
  23. }
  24. // 检查API密钥格式(app_id:app_secret)
  25. if (!str_contains($apiKey, ':')) {
  26. $this->addError($arg, 'API密钥格式错误,应为 app_id:app_secret 格式');
  27. return;
  28. }
  29. $parts = explode(':', $apiKey, 2);
  30. if (count($parts) !== 2) {
  31. $this->addError($arg, 'API密钥格式错误');
  32. return;
  33. }
  34. [$appId, $appSecret] = $parts;
  35. if (empty($appId) || empty($appSecret)) {
  36. $this->addError($arg, 'API密钥的应用ID和密钥都不能为空');
  37. return;
  38. }
  39. // 验证应用ID格式(32位字符)
  40. if (strlen($appId) !== 32 || !ctype_alnum($appId)) {
  41. $this->addError($arg, '应用ID格式错误');
  42. return;
  43. }
  44. // 验证应用密钥格式(64位字符)
  45. if (strlen($appSecret) !== 64 || !ctype_alnum($appSecret)) {
  46. $this->addError($arg, '应用密钥格式错误');
  47. return;
  48. }
  49. }
  50. /**
  51. * 验证JWT令牌格式
  52. *
  53. * @param string $token
  54. * @param string $arg
  55. * @return void
  56. */
  57. public function validateJwtToken(string $token, string $arg): void
  58. {
  59. if (empty($token)) {
  60. $this->addError($arg, 'JWT令牌不能为空');
  61. return;
  62. }
  63. // JWT令牌应该有3个部分,用.分隔
  64. $parts = explode('.', $token);
  65. if (count($parts) !== 3) {
  66. $this->addError($arg, 'JWT令牌格式错误');
  67. return;
  68. }
  69. // 验证每个部分都是有效的base64编码
  70. foreach ($parts as $index => $part) {
  71. if (empty($part)) {
  72. $this->addError($arg, 'JWT令牌格式错误');
  73. return;
  74. }
  75. // 尝试base64解码
  76. $decoded = base64_decode($part, true);
  77. if ($decoded === false) {
  78. $this->addError($arg, 'JWT令牌格式错误');
  79. return;
  80. }
  81. }
  82. }
  83. /**
  84. * 验证签名参数
  85. *
  86. * @param array $params
  87. * @param string $arg
  88. * @return void
  89. */
  90. public function validateSignatureParams(array $params, string $arg): void
  91. {
  92. $requiredParams = ['timestamp', 'nonce', 'signature'];
  93. foreach ($requiredParams as $param) {
  94. if (!isset($params[$param]) || empty($params[$param])) {
  95. $this->addError($arg, "缺少必需的签名参数: {$param}");
  96. return;
  97. }
  98. }
  99. // 验证时间戳
  100. $timestamp = $params['timestamp'];
  101. if (!is_numeric($timestamp)) {
  102. $this->addError($arg, '时间戳格式错误');
  103. return;
  104. }
  105. // 检查时间戳是否在合理范围内(5分钟内)
  106. $currentTime = time();
  107. $timeDiff = abs($currentTime - $timestamp);
  108. if ($timeDiff > 300) { // 5分钟
  109. $this->addError($arg, '请求时间戳过期');
  110. return;
  111. }
  112. // 验证随机数
  113. $nonce = $params['nonce'];
  114. if (strlen($nonce) < 8) {
  115. $this->addError($arg, '随机数长度不足');
  116. return;
  117. }
  118. // 验证签名格式
  119. $signature = $params['signature'];
  120. if (strlen($signature) !== 64 || !ctype_xdigit($signature)) {
  121. $this->addError($arg, '签名格式错误');
  122. return;
  123. }
  124. }
  125. /**
  126. * 验证IP地址格式
  127. *
  128. * @param string $ip
  129. * @param string $arg
  130. * @return void
  131. */
  132. public function validateIpAddress(string $ip, string $arg): void
  133. {
  134. if (empty($ip)) {
  135. $this->addError($arg, 'IP地址不能为空');
  136. return;
  137. }
  138. // 验证IPv4或IPv6地址
  139. if (!filter_var($ip, FILTER_VALIDATE_IP)) {
  140. $this->addError($arg, 'IP地址格式错误');
  141. return;
  142. }
  143. }
  144. /**
  145. * 验证IP白名单格式
  146. *
  147. * @param array $ipList
  148. * @param string $arg
  149. * @return void
  150. */
  151. public function validateIpWhitelist(array $ipList, string $arg): void
  152. {
  153. if (empty($ipList)) {
  154. $this->addError($arg, 'IP白名单不能为空');
  155. return;
  156. }
  157. foreach ($ipList as $index => $ip) {
  158. if (empty($ip)) {
  159. $this->addError($arg, "第" . ($index + 1) . "个IP地址不能为空");
  160. continue;
  161. }
  162. // 支持CIDR格式
  163. if (str_contains($ip, '/')) {
  164. if (!$this->validateCidr($ip)) {
  165. $this->addError($arg, "第" . ($index + 1) . "个IP地址CIDR格式错误: {$ip}");
  166. }
  167. } else {
  168. // 支持通配符
  169. if (str_contains($ip, '*')) {
  170. if (!$this->validateWildcardIp($ip)) {
  171. $this->addError($arg, "第" . ($index + 1) . "个IP地址通配符格式错误: {$ip}");
  172. }
  173. } else {
  174. // 普通IP地址
  175. if (!filter_var($ip, FILTER_VALIDATE_IP)) {
  176. $this->addError($arg, "第" . ($index + 1) . "个IP地址格式错误: {$ip}");
  177. }
  178. }
  179. }
  180. }
  181. }
  182. /**
  183. * 验证CIDR格式
  184. *
  185. * @param string $cidr
  186. * @return bool
  187. */
  188. protected function validateCidr(string $cidr): bool
  189. {
  190. $parts = explode('/', $cidr);
  191. if (count($parts) !== 2) {
  192. return false;
  193. }
  194. [$ip, $mask] = $parts;
  195. // 验证IP地址
  196. if (!filter_var($ip, FILTER_VALIDATE_IP)) {
  197. return false;
  198. }
  199. // 验证子网掩码
  200. if (!is_numeric($mask)) {
  201. return false;
  202. }
  203. $mask = (int)$mask;
  204. if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
  205. // IPv4
  206. return $mask >= 0 && $mask <= 32;
  207. } else {
  208. // IPv6
  209. return $mask >= 0 && $mask <= 128;
  210. }
  211. }
  212. /**
  213. * 验证通配符IP格式
  214. *
  215. * @param string $ip
  216. * @return bool
  217. */
  218. protected function validateWildcardIp(string $ip): bool
  219. {
  220. // 将*替换为0进行验证
  221. $testIp = str_replace('*', '0', $ip);
  222. return filter_var($testIp, FILTER_VALIDATE_IP) !== false;
  223. }
  224. /**
  225. * 验证频率限制配置
  226. *
  227. * @param array $rateLimits
  228. * @param string $arg
  229. * @return void
  230. */
  231. public function validateRateLimits(array $rateLimits, string $arg): void
  232. {
  233. $validTypes = [
  234. 'requests_per_minute',
  235. 'requests_per_hour',
  236. 'requests_per_day',
  237. 'requests_per_week',
  238. 'requests_per_month'
  239. ];
  240. foreach ($rateLimits as $type => $limit) {
  241. if (!in_array($type, $validTypes)) {
  242. $this->addError($arg, "无效的限制类型: {$type}");
  243. continue;
  244. }
  245. if (!is_numeric($limit) || $limit < 0) {
  246. $this->addError($arg, "限制值必须是非负数: {$type}");
  247. continue;
  248. }
  249. // 检查限制值的合理性
  250. $maxLimits = [
  251. 'requests_per_minute' => 1000,
  252. 'requests_per_hour' => 10000,
  253. 'requests_per_day' => 100000,
  254. 'requests_per_week' => 500000,
  255. 'requests_per_month' => 2000000,
  256. ];
  257. if ($limit > $maxLimits[$type]) {
  258. $this->addError($arg, "限制值过大: {$type} 最大值为 {$maxLimits[$type]}");
  259. }
  260. }
  261. }
  262. /**
  263. * 验证权限范围
  264. *
  265. * @param array $scopes
  266. * @param string $arg
  267. * @return void
  268. */
  269. public function validateScopes(array $scopes, string $arg): void
  270. {
  271. if (empty($scopes)) {
  272. $this->addError($arg, '权限范围不能为空');
  273. return;
  274. }
  275. $validScopes = [
  276. 'USER_READ', 'USER_WRITE',
  277. 'GAME_READ', 'GAME_WRITE',
  278. 'ITEM_READ', 'ITEM_WRITE',
  279. 'FUND_READ', 'FUND_WRITE',
  280. 'TRADE_READ', 'TRADE_WRITE',
  281. 'ADMIN_READ', 'ADMIN_WRITE',
  282. 'SYSTEM_READ', 'SYSTEM_WRITE',
  283. 'NOTIFICATION_READ', 'NOTIFICATION_WRITE',
  284. 'LOG_READ',
  285. '*', 'ADMIN'
  286. ];
  287. foreach ($scopes as $scope) {
  288. if (!in_array($scope, $validScopes)) {
  289. $this->addError($arg, "无效的权限范围: {$scope}");
  290. }
  291. }
  292. }
  293. }