| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- <?php
- namespace Longyi\RewardPoints\Http\Middleware;
- use Closure;
- use Illuminate\Http\Request;
- use Symfony\Component\HttpFoundation\Response;
- use Laravel\Sanctum\PersonalAccessToken;
- use Webkul\BagistoApi\Services\ApiKeyService;
- class ApiCustomerAuthenticate
- {
- protected ?ApiKeyService $apiKeyService = null;
- /**
- * Handle an incoming request.
- *
- * 支持三种认证方式:
- * 1. Session Cookie(传统 Web 登录)
- * 2. Sanctum Bearer Token(BagistoApi 登录)
- * 3. Storefront Key(X-STOREFRONT-KEY 请求头)→ 由 BagistoApi 的 ApiKeyService 统一验证
- *
- * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
- */
- public function handle(Request $request, Closure $next): Response
- {
- // 方式 3: Storefront Key 认证(X-STOREFRONT-KEY 请求头)
- $storefrontKey = $request->header('X-STOREFRONT-KEY');
- if ($storefrontKey) {
- $apiKeyService = $this->getApiKeyService();
- // 验证 key 有效性
- $validation = $apiKeyService->validate(
- $storefrontKey,
- ApiKeyService::KEY_TYPE_SHOP,
- $request->ip()
- );
- if (! ($validation['valid'] ?? false)) {
- return response()->json([
- 'error' => 'invalid_storefront_key',
- 'message' => $validation['message'] ?? 'The provided storefront key is invalid or inactive.',
- ], 403);
- }
- // 频率限制检查
- $rateLimit = $apiKeyService->checkRateLimit($validation['client']);
- if (! ($rateLimit['allowed'] ?? false)) {
- return response()->json([
- 'error' => 'rate_limit_exceeded',
- 'message' => 'Rate limit exceeded. Please retry later.',
- 'retry_after' => $rateLimit['reset_at'] ?? 60,
- ], 429);
- }
- return $next($request);
- } else {
- return response()->json([
- 'error' => 'invalid_storefront_key',
- 'message' => 'X-STOREFRONT-KEY header is required',
- ], 403);
- }
- // 方式 1: Session Cookie 认证(传统 Web 登录)
- if (auth()->guard('customer')->check()) {
- return $next($request);
- }
- // 方式 2: Sanctum Bearer Token 认证(BagistoApi 登录)
- if ($request->bearerToken()) {
- $customer = $this->authenticateViaSanctum($request);
- if ($customer) {
- // 将 Sanctum 认证的用户设置到 customer guard
- auth()->guard('customer')->setUser($customer);
- return $next($request);
- }
- }
- // 都未认证,返回 JSON 错误
- return response()->json([
- 'error' => 'Please login first',
- 'message' => 'Authentication required. Please login via /customer/login (Session) or /api/shop/customer/login (Token)'
- ], 401);
- }
- /**
- * 懒加载 ApiKeyService 实例
- */
- protected function getApiKeyService(): ApiKeyService
- {
- return $this->apiKeyService ??= app(ApiKeyService::class);
- }
- /**
- * 通过 Sanctum Token 验证客户
- *
- * @return \Webkul\Customer\Models\Customer|null
- */
- protected function authenticateViaSanctum(Request $request)
- {
- $token = $request->bearerToken();
- if (!$token) {
- return null;
- }
- try {
- // 使用 Sanctum 的 PersonalAccessToken 模型验证
- $accessToken = PersonalAccessToken::findToken($token);
- if (!$accessToken) {
- return null;
- }
- // 检查 Token 是否过期
- if ($accessToken->expires_at && $accessToken->expires_at->isPast()) {
- return null;
- }
- // 获取关联的用户模型
- $tokenable = $accessToken->tokenable;
- // 确保是 Customer 模型
- if (!$tokenable || !($tokenable instanceof \Webkul\Customer\Models\Customer)) {
- return null;
- }
- // 更新 Token 最后使用时间
- $accessToken->touch();
- return $tokenable;
- } catch (\Exception $e) {
- // 记录错误日志(可选)
- \Log::debug('Sanctum authentication failed', [
- 'error' => $e->getMessage()
- ]);
- return null;
- }
- }
- }
|