Laravel 8.x如何校验表单值是否被客户端篡改后发送至PayPal
在Laravel 8.x中防止PayPal表单值篡改的解决方案
你当前的表单直接提交到PayPal沙箱,客户端可通过开发者工具修改隐藏字段,以下是两种可靠的校验方案:
方案一:服务器端中转校验(快速可控)
不让表单直接提交到PayPal,先提交到Laravel控制器完成校验,再转发请求到PayPal。
1. 修改表单提交地址
将表单的action改为自定义Laravel路由:
<form id="fFlightPrice" method="post" action="{{ route('paypal.proxy') }}"> @csrf <div class="my-3"> <input type="hidden" name="cmd" value="_cart"> <input type="hidden" name="upload" value="1"> <input type="hidden" name="no_note" value="0"> <input type="hidden" name="bn" value="RB_BuyNow_WPS_IT"> <input type="hidden" name="tax_cart" value="0.00"> <input type="hidden" name="rm" value="2"> <input type="hidden" name="business" value="{{ env('PAYPAL_BUSINNESS_EMAIL') }}"> <input type="hidden" name="handling_cart" value="0"> <input type="hidden" name="currency_code" value="EUR"> <input type="hidden" name="lc" value="IT"> <input type="hidden" name="return" value="{{ route(P::ROUTE_PAYPAL_RETURN) }}"> <input type="hidden" name="cbt" value="Torna al sito"> <input type="hidden" name="cancel_return" value="{{ route(P::ROUTE_PAYPAL_CANCEL) }}"> </div> <div class="my-3"> @forelse($flights as $flight) <input type="hidden" name="item_name_{{ $loop->iteration }}" value="{{ $flight['name'] }}"> <input type="hidden" name="item_number_{{ $loop->iteration }}" value="{{ $flight['id'] }}"> <input type="hidden" name="amount_{{ $loop->iteration}}" value="{{ $flight['flight_price'] }}"> @empty @endforelse </div> <div class="text-center"> <button type="submit" class="btn btn-primary btn-lg">PAGA</button> </div> </form>
2. 创建路由与控制器
在routes/web.php添加路由:
Route::post('/paypal/proxy', [PayPalProxyController::class, 'handle'])->name('paypal.proxy');
创建PayPalProxyController实现校验与转发:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; class PayPalProxyController extends Controller { public function handle(Request $request) { // 从会话/数据库获取原始航班数据(需提前存储,比如生成表单时存入session) $originalFlights = session('selected_flights'); // 校验航班数据一致性 $this->validateFlightData($request, $originalFlights); // 校验固定字段合法性 $this->validateFixedFields($request); // 转发请求到PayPal return Http::asForm()->post(env('PAYPAL_FORM_URL'), $request->all()); } private function validateFlightData(Request $request, array $originalFlights) { foreach ($originalFlights as $index => $flight) { $iteration = $index + 1; if ($request->input("item_name_{$iteration}") !== $flight['name']) { abort(403, '商品名称被篡改'); } if ($request->input("item_number_{$iteration}") !== $flight['id']) { abort(403, '商品ID被篡改'); } // 用bccomp避免浮点精度问题 if (bccomp($request->input("amount_{$iteration}"), $flight['flight_price'], 2) !== 0) { abort(403, '商品金额被篡改'); } } } private function validateFixedFields(Request $request) { $expected = [ 'cmd' => '_cart', 'upload' => '1', 'currency_code' => 'EUR', 'business' => env('PAYPAL_BUSINNESS_EMAIL'), ]; foreach ($expected as $field => $value) { if ($request->input($field) !== $value) { abort(403, "字段{$field}被篡改"); } } } }
方案二:PayPal官方加密签名(合规安全)
使用PayPal证书加密表单字段,篡改后PayPal会直接拒绝请求。
1. 获取PayPal证书
登录PayPal沙箱账号,进入「卖家工具」->「加密付款按钮」,下载自身证书、私钥及PayPal公钥证书,存入项目storage/paypal目录。
2. 服务器端生成加密字段
在生成表单的控制器中,构建PayPal字段并加密:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class PayPalFormController extends Controller { public function showForm() { $flights = // 从数据库获取航班数据 $paypalFields = $this->buildPayPalFields($flights); $encrypted = $this->encryptFields($paypalFields); return view('paypal.form', compact('encrypted')); } private function buildPayPalFields(array $flights) { $fields = [ 'cmd' => '_cart', 'upload' => '1', 'no_note' => '0', 'bn' => 'RB_BuyNow_WPS_IT', 'tax_cart' => '0.00', 'rm' => '2', 'business' => env('PAYPAL_BUSINNESS_EMAIL'), 'handling_cart' => '0', 'currency_code' => 'EUR', 'lc' => 'IT', 'return' => route(P::ROUTE_PAYPAL_RETURN), 'cbt' => 'Torna al sito', 'cancel_return' => route(P::ROUTE_PAYPAL_CANCEL), ]; foreach ($flights as $index => $flight) { $iteration = $index + 1; $fields["item_name_{$iteration}"] = $flight['name']; $fields["item_number_{$iteration}"] = $flight['id']; $fields["amount_{$iteration}"] = $flight['flight_price']; } return $fields; } private function encryptFields(array $fields) { $myCert = storage_path('paypal/my_cert.pem'); $payPalCert = storage_path('paypal/paypal_cert.pem'); $privateKey = storage_path('paypal/private_key.pem'); // 拼接字段为PayPal要求的格式 $fieldStr = ''; foreach ($fields as $key => $value) { $fieldStr .= "$key=$value\n"; } // 加密 openssl_pkcs7_encrypt( $fieldStr, $encrypted, file_get_contents($payPalCert), [file_get_contents($privateKey), ''], PKCS7_BINARY | PKCS7_DETACHED ); // 清理加密内容的头部尾部 $encrypted = preg_replace('/-----BEGIN PKCS7-----\n/', '', $encrypted); $encrypted = preg_replace('/\n-----END PKCS7-----/', '', $encrypted); $encrypted = str_replace("\n", '', $encrypted); return $encrypted; } }
3. 简化前端表单
表单仅保留加密字段,直接提交到PayPal:
<form id="fFlightPrice" method="post" action="{{ env('PAYPAL_FORM_URL') }}"> <input type="hidden" name="encrypted" value="{{ $encrypted }}"> <div class="text-center"> <button type="submit" class="btn btn-primary btn-lg">PAGA</button> </div> </form>
方案对比
- 方案一:快速实现,完全可控,无需依赖PayPal证书机制。
- 方案二:符合PayPal官方安全规范,适合高安全要求场景。
内容的提问来源于stack exchange,提问作者Ssssteven
相关产品推荐
相关产品推荐

