ScopeMiddleware.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. namespace App\Module\OpenAPI\Middleware;
  3. use App\Module\OpenAPI\Enums\SCOPE_TYPE;
  4. use App\Module\OpenAPI\Models\OpenApiApp;
  5. use App\Module\OpenAPI\Services\OpenApiService;
  6. use Closure;
  7. use Illuminate\Http\Request;
  8. use Illuminate\Support\Facades\Log;
  9. /**
  10. * API权限范围中间件
  11. *
  12. * 用于验证应用是否具有访问特定API的权限
  13. */
  14. class ScopeMiddleware
  15. {
  16. protected OpenApiService $openApiService;
  17. public function __construct(OpenApiService $openApiService)
  18. {
  19. $this->openApiService = $openApiService;
  20. }
  21. /**
  22. * 处理请求
  23. *
  24. * @param Request $request
  25. * @param Closure $next
  26. * @param string $requiredScope 必需的权限范围
  27. * @return mixed
  28. */
  29. public function handle(Request $request, Closure $next, string $requiredScope)
  30. {
  31. // 获取应用信息
  32. $app = $this->getAppFromRequest($request);
  33. if (!$app) {
  34. return $this->forbiddenResponse('应用信息不存在');
  35. }
  36. // 检查权限范围
  37. if (!$this->checkScope($app, $requiredScope)) {
  38. return $this->forbiddenResponse("缺少必需的权限范围: {$requiredScope}");
  39. }
  40. // 记录权限检查日志
  41. $this->logScopeCheck($app, $requiredScope, $request);
  42. return $next($request);
  43. }
  44. /**
  45. * 从请求中获取应用信息
  46. *
  47. * @param Request $request
  48. * @return OpenApiApp|null
  49. */
  50. protected function getAppFromRequest(Request $request): ?OpenApiApp
  51. {
  52. // 从请求属性中获取应用信息(由认证中间件设置)
  53. return $request->attributes->get('openapi_app');
  54. }
  55. /**
  56. * 检查权限范围
  57. *
  58. * @param OpenApiApp $app
  59. * @param string $requiredScope
  60. * @return bool
  61. */
  62. protected function checkScope(OpenApiApp $app, string $requiredScope): bool
  63. {
  64. // 获取应用的权限范围
  65. $appScopes = $app->scopes ?? [];
  66. // 如果应用没有配置权限范围,拒绝访问
  67. if (empty($appScopes)) {
  68. return false;
  69. }
  70. // 检查是否有管理员权限(拥有所有权限)
  71. if (in_array('ADMIN', $appScopes)) {
  72. return true;
  73. }
  74. // 检查是否直接拥有所需权限
  75. if (in_array($requiredScope, $appScopes)) {
  76. return true;
  77. }
  78. // 检查权限依赖关系
  79. return $this->checkScopeDependencies($appScopes, $requiredScope);
  80. }
  81. /**
  82. * 检查权限依赖关系
  83. *
  84. * @param array $appScopes 应用拥有的权限
  85. * @param string $requiredScope 需要的权限
  86. * @return bool
  87. */
  88. protected function checkScopeDependencies(array $appScopes, string $requiredScope): bool
  89. {
  90. // 获取权限枚举实例
  91. $scopeEnum = SCOPE_TYPE::tryFrom($requiredScope);
  92. if (!$scopeEnum) {
  93. return false;
  94. }
  95. // 检查权限依赖
  96. $dependencies = $scopeEnum->getDependencies();
  97. foreach ($dependencies as $dependency) {
  98. if (in_array($dependency, $appScopes)) {
  99. return true;
  100. }
  101. }
  102. // 检查权限继承(如果有写权限,通常也有读权限)
  103. if (str_ends_with($requiredScope, '_READ')) {
  104. $writeScope = str_replace('_READ', '_WRITE', $requiredScope);
  105. if (in_array($writeScope, $appScopes)) {
  106. return true;
  107. }
  108. }
  109. return false;
  110. }
  111. /**
  112. * 记录权限检查日志
  113. *
  114. * @param OpenApiApp $app
  115. * @param string $requiredScope
  116. * @param Request $request
  117. * @return void
  118. */
  119. protected function logScopeCheck(OpenApiApp $app, string $requiredScope, Request $request): void
  120. {
  121. Log::info("Scope check passed", [
  122. 'app_id' => $app->app_id,
  123. 'required_scope' => $requiredScope,
  124. 'app_scopes' => $app->scopes,
  125. 'uri' => $request->getRequestUri(),
  126. 'method' => $request->getMethod(),
  127. 'ip' => $request->ip(),
  128. ]);
  129. }
  130. /**
  131. * 返回权限不足响应
  132. *
  133. * @param string $message
  134. * @return \Illuminate\Http\JsonResponse
  135. */
  136. protected function forbiddenResponse(string $message)
  137. {
  138. return response()->json([
  139. 'error' => 'insufficient_scope',
  140. 'message' => $message,
  141. 'required_scopes' => func_get_args()[1] ?? null,
  142. ], 403);
  143. }
  144. }