如何防范基于Laravel v11 Livewire的结账系统遭BIN攻击?
防范BIN攻击的解决方案(基于Laravel+Livewire+Stripe)
一、请求限流:拦截高频重复请求
利用Laravel原生限流功能,针对IP、邮箱等维度限制请求频率,在支付初始化前拦截恶意高频请求。
实现步骤:
- 在Livewire组件中引入限流门面:
use Illuminate\Support\Facades\RateLimiter;
- 在
initializePayment方法开头添加限流逻辑:
public function initializePayment() { // IP维度限流:1分钟内最多5次请求 $ipKey = 'checkout:'.$this->getClientIp(); if (!RateLimiter::attempt($ipKey, 5)) { $this->addError('rate_limit', '请求过于频繁,请1分钟后再试'); return; } // 邮箱维度限流(若用户已提供邮箱) if (!empty($this->email)) { $emailKey = 'checkout:email:'.$this->email; if (!RateLimiter::attempt($emailKey, 3)) { $this->addError('rate_limit_email', '该邮箱请求过于频繁,请稍后再试'); return; } } // 原有支付逻辑... }
- 新增获取客户端IP的方法:
private function getClientIp() { return request()->ip(); }
二、验证码验证:区分人机访问
当检测到可疑行为(如支付失败次数超标)时,强制要求用户完成验证码验证,拦截机器人攻击。
实现步骤:
- 安装并配置验证码包(如
mews/captcha),在Livewire组件中添加验证码字段:
public $captcha;
- 更新组件验证规则:
protected $rules = [ // 原有字段验证规则... 'captcha' => 'required|captcha', ];
- 在结账模板中添加验证码输入区域:
<div> <label>验证码</label> <input type="text" wire:model="captcha"> <img src="{{ captcha_src() }}" onclick="this.src='{{ captcha_src() }}?' + Math.random()"> @error('captcha') <span class="text-red-500">{{ $message }}</span> @enderror </div>
- 在支付初始化方法中添加验证码校验逻辑:
public function initializePayment() { $ip = $this->getClientIp(); $failureCount = Redis::get('checkout:failures:'.$ip) ?? 0; // 失败次数超过3次时验证验证码 if ($failureCount >= 3) { $this->validate(['captcha' => 'required|captcha']); } // 原有限流、支付逻辑... }
三、可疑活动记录与拦截
通过Redis记录IP、邮箱的支付失败次数,达到阈值后直接拦截请求。
实现步骤:
- 在Stripe支付失败回调(取消页面或Webhook)中记录失败次数:
public function handlePaymentFailure($sessionId) { $stripe = new StripeClient(env('STRIPE_SECRET')); $session = $stripe->checkout->sessions->retrieve($sessionId); $ip = request()->ip(); // 记录IP失败次数,有效期1小时 Redis::incr('checkout:failures:'.$ip); Redis::expire('checkout:failures:'.$ip, 3600); // 记录邮箱失败次数(若存在) if ($session->customer_email) { Redis::incr('checkout:failures:email:'.$session->customer_email); Redis::expire('checkout:failures:email:'.$session->customer_email, 3600); } }
- 在支付初始化方法中添加失败次数检查:
public function initializePayment() { $ip = $this->getClientIp(); $failureCount = Redis::get('checkout:failures:'.$ip) ?? 0; // 失败次数超过5次时直接拦截 if ($failureCount >= 5) { $this->addError('blocked', '该IP存在异常支付行为,请1小时后再试'); return; } // 原有限流、验证码逻辑... }
四、Stripe层面防护
利用Stripe原生功能减少攻击影响:
- 启用Stripe Radar:配置自定义规则拦截同一BIN的多次失败尝试,自动识别欺诈交易
- 启用支付方法验证:在创建支付会话前验证卡片有效性,减少无效请求传递到支付网关
- 配置Webhook:实时监听支付失败事件,及时更新本地失败次数记录
五、优化后的完整initializePayment示例
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\Redis; public function initializePayment() { $ip = $this->getClientIp(); $failureCount = Redis::get('checkout:failures:'.$ip) ?? 0; // 1. 拦截异常IP if ($failureCount >= 5) { $this->addError('blocked', '该IP存在异常支付行为,请1小时后再试'); return; } // 2. IP维度限流 $ipKey = 'checkout:'.$ip; if (!RateLimiter::attempt($ipKey, 5)) { $this->addError('rate_limit', '请求过于频繁,请1分钟后再试'); return; } // 3. 邮箱维度限流 if (!empty($this->email)) { $emailKey = 'checkout:email:'.$this->email; if (!RateLimiter::attempt($emailKey, 3)) { $this->addError('rate_limit_email', '该邮箱请求过于频繁,请稍后再试'); return; } } // 4. 验证码校验(失败次数超标时) if ($failureCount >= 3) { $this->validate(['captcha' => 'required|captcha']); } // 原有支付逻辑 $application = Application::findOrFail($this->applicationId); $stripe = new StripeClient(env('STRIPE_SECRET')); $lineItem = [ [ 'price_data' => [ 'currency' => 'usd', 'product_data' => [ 'name' => 'Test Name', 'description' => "Description", 'images' => [asset('assets/images/1.jpeg')], ], 'unit_amount' => 49 * 100, ], 'quantity' => 1, ], ]; $stripeSession = $stripe->checkout->sessions->create([ 'payment_method_types' => ['card'], 'line_items' => $lineItem, 'mode' => 'payment', 'metadata' => [ 'application_id' => $application->id, 'application_number' => $application->application_number, ], 'customer_email' => $this->email, 'payment_intent_data' => [ 'description' => "Payment for Application #{$application->application_number}", 'metadata' => [ 'application_id' => $application->id, 'application_number' => $application->application_number, 'email' => $this->email, 'phone' => $this->phone_number, 'first_name' => $this->first_name, 'last_name' => $this->last_name, ], ], 'success_url' => route('stripe.success', [], true) . '?session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => route('stripe.cancel', [], true) . '?session_id={CHECKOUT_SESSION_ID}', ]); return redirect()->away($stripeSession->url); } private function getClientIp() { return request()->ip(); }
内容的提问来源于stack exchange,提问作者inżynier umair
相关产品推荐
相关产品推荐

