ApiCustomerAuthenticate.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. <?php
  2. namespace Longyi\RewardPoints\Http\Middleware;
  3. use Closure;
  4. use Illuminate\Http\Request;
  5. use Symfony\Component\HttpFoundation\Response;
  6. use Laravel\Sanctum\PersonalAccessToken;
  7. use Webkul\BagistoApi\Services\ApiKeyService;
  8. class ApiCustomerAuthenticate
  9. {
  10. protected ?ApiKeyService $apiKeyService = null;
  11. /**
  12. * Handle an incoming request.
  13. *
  14. * 支持三种认证方式:
  15. * 1. Session Cookie(传统 Web 登录)
  16. * 2. Sanctum Bearer Token(BagistoApi 登录)
  17. * 3. Storefront Key(X-STOREFRONT-KEY 请求头)→ 由 BagistoApi 的 ApiKeyService 统一验证
  18. *
  19. * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
  20. */
  21. public function handle(Request $request, Closure $next): Response
  22. {
  23. // 方式 3: Storefront Key 认证(X-STOREFRONT-KEY 请求头)
  24. $storefrontKey = $request->header('X-STOREFRONT-KEY');
  25. if ($storefrontKey) {
  26. $apiKeyService = $this->getApiKeyService();
  27. // 验证 key 有效性
  28. $validation = $apiKeyService->validate(
  29. $storefrontKey,
  30. ApiKeyService::KEY_TYPE_SHOP,
  31. $request->ip()
  32. );
  33. if (! ($validation['valid'] ?? false)) {
  34. return response()->json([
  35. 'error' => 'invalid_storefront_key',
  36. 'message' => $validation['message'] ?? 'The provided storefront key is invalid or inactive.',
  37. ], 403);
  38. }
  39. // 频率限制检查
  40. $rateLimit = $apiKeyService->checkRateLimit($validation['client']);
  41. if (! ($rateLimit['allowed'] ?? false)) {
  42. return response()->json([
  43. 'error' => 'rate_limit_exceeded',
  44. 'message' => 'Rate limit exceeded. Please retry later.',
  45. 'retry_after' => $rateLimit['reset_at'] ?? 60,
  46. ], 429);
  47. }
  48. return $next($request);
  49. } else {
  50. return response()->json([
  51. 'error' => 'invalid_storefront_key',
  52. 'message' => 'X-STOREFRONT-KEY header is required',
  53. ], 403);
  54. }
  55. // 方式 1: Session Cookie 认证(传统 Web 登录)
  56. if (auth()->guard('customer')->check()) {
  57. return $next($request);
  58. }
  59. // 方式 2: Sanctum Bearer Token 认证(BagistoApi 登录)
  60. if ($request->bearerToken()) {
  61. $customer = $this->authenticateViaSanctum($request);
  62. if ($customer) {
  63. // 将 Sanctum 认证的用户设置到 customer guard
  64. auth()->guard('customer')->setUser($customer);
  65. return $next($request);
  66. }
  67. }
  68. // 都未认证,返回 JSON 错误
  69. return response()->json([
  70. 'error' => 'Please login first',
  71. 'message' => 'Authentication required. Please login via /customer/login (Session) or /api/shop/customer/login (Token)'
  72. ], 401);
  73. }
  74. /**
  75. * 懒加载 ApiKeyService 实例
  76. */
  77. protected function getApiKeyService(): ApiKeyService
  78. {
  79. return $this->apiKeyService ??= app(ApiKeyService::class);
  80. }
  81. /**
  82. * 通过 Sanctum Token 验证客户
  83. *
  84. * @return \Webkul\Customer\Models\Customer|null
  85. */
  86. protected function authenticateViaSanctum(Request $request)
  87. {
  88. $token = $request->bearerToken();
  89. if (!$token) {
  90. return null;
  91. }
  92. try {
  93. // 使用 Sanctum 的 PersonalAccessToken 模型验证
  94. $accessToken = PersonalAccessToken::findToken($token);
  95. if (!$accessToken) {
  96. return null;
  97. }
  98. // 检查 Token 是否过期
  99. if ($accessToken->expires_at && $accessToken->expires_at->isPast()) {
  100. return null;
  101. }
  102. // 获取关联的用户模型
  103. $tokenable = $accessToken->tokenable;
  104. // 确保是 Customer 模型
  105. if (!$tokenable || !($tokenable instanceof \Webkul\Customer\Models\Customer)) {
  106. return null;
  107. }
  108. // 更新 Token 最后使用时间
  109. $accessToken->touch();
  110. return $tokenable;
  111. } catch (\Exception $e) {
  112. // 记录错误日志(可选)
  113. \Log::debug('Sanctum authentication failed', [
  114. 'error' => $e->getMessage()
  115. ]);
  116. return null;
  117. }
  118. }
  119. }