首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 防惡意刷新/刷票核心原理與三大實戰方案

     編輯:PHP 相關知識

PHP 防惡意刷新/刷票核心原理與三大實戰方案

惡意刷新 (F5 攻擊)、表單重複提交、投票刷票、API 暴力調用——這些行為不僅汙染數據、消耗資源,更可能觸發業務邏輯漏洞 (如重複扣款)。本文從核心原理出發,梳理 Token 驗證、PRG 模式、Session/Cookie 限流 三大經典方案,並給出生產級代碼範例與避坑指南。

核心防禦原理:一次性 Token + 狀態機

所有有效防禦的本質都是將「用戶操作」與「服務端狀態」綁定:

  1. 生成階段:頁面渲染時,服務端生成唯一、不可預測、一次性的 Token,存入 Session,同時輸出到表單隱藏域/URL 參數/JS 變量。
  2. 驗證階段:用戶提交時,服務端比對提交的 Token 與 Session 中的 Token:
    ✅ 一致 → 業務處理 → 銷毀/更新 Session Token (防重放)
    ❌ 不一致/缺失 → 拒絕處理,提示「請勿重複提交/頁面已過期」
  3. 時效性:Token 設置有效期 (如 30 分鐘),過期自動失效,防止長期佔用 Session 空間。

方案一:Token + Session —— 通用表單/鏈接防重複

<?php
// ==== 通用 Token 工具類 ====
class CsrfToken
{
    private const TOKEN_KEY = '_csrf_tokens'; // Session 中存儲結構: [token => expire_timestamp]
    private const TOKEN_TTL = 1800; // 30分鐘

    public static function generate(): string
    {
        $token = bin2hex(random_bytes(32));
        $tokens = $_SESSION[self::TOKEN_KEY] ?? [];
        $tokens[$token] = time() + self::TOKEN_TTL;
        $_SESSION[self::TOKEN_KEY] = $tokens;
        return $token;
    }

    public static function validate(string $token): bool
    {
        $tokens = $_SESSION[self::TOKEN_KEY] ?? [];
        if (!isset($tokens[$token])) {
            return false; // Token 不存在/已用過
        }
        if ($tokens[$token] < time()) {
            unset($tokens[$token]);
            $_SESSION[self::TOKEN_KEY] = $tokens;
            return false; // Token 過期
        }
        // 一次性:驗證通過即刪除
        unset($tokens[$token]);
        $_SESSION[self::TOKEN_KEY] = $tokens;
        return true;
    }

    public static function cleanExpired(): void
    {
        $tokens = $_SESSION[self::TOKEN_KEY] ?? [];
        $now = time();
        foreach ($tokens as $k => $expire) {
            if ($expire < $now) unset($tokens[$k]);
        }
        $_SESSION[self::TOKEN_KEY] = $tokens;
    }
}

// ==== 表單頁面 (form.php) ====
session_start();
CsrfToken::cleanExpired();
$token = CsrfToken::generate();
?>
// ==== 處理頁面 (submit.php) ==== session_start(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { $token = $_POST['csrf_token'] ?? ''; if (!CsrfToken::validate($token)) { die('非法請求或重複提交,請返回重試'); } // ✅ 業務邏輯:寫入投票、扣款等 echo '提交成功!'; }

優點:通用性強,適用所有表單/鏈接;缺點:依賴 Session (需 Cookie),多標籤頁操作時 Token 互斥體驗差。

方案二:PRG 模式 (Post-Redirect-Get) —— 徹底解決 F5 重複提交

核心思想:POST 處理完畢立即 302 重定向到 GET 頁面,瀏覽器歷史記錄中只剩 GET,用戶 F5 只會重新 GET,不會再次 POST。

<?php
// vote.php —— 合併顯示與處理
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 1. 可選:結合 Token 二次驗證
    if (!CsrfToken::validate($_POST['csrf_token'] ?? '')) {
        $_SESSION['flash_error'] = '請勿重複提交';
    } else {
        // 2. 業務處理
        $pdo->exec("UPDATE votes SET count = count + 1 WHERE id = {$_POST['item_id']}");
        $_SESSION['flash_success'] = '投票成功!';
    }
    // 3. 關鍵:302 重定向到自身 (GET)
    header('Location: ' . $_SERVER['PHP_SELF'], true, 302);
    exit;
}

// GET 請求:顯示頁面與閃存訊息
$error = $_SESSION['flash_error'] ?? null;
$success = $_SESSION['flash_success'] ?? null;
unset($_SESSION['flash_error'], $_SESSION['flash_success']);
$token = CsrfToken::generate();
?>



優點:徹底消除 F5 重複提交,無需 JS 干預;缺點:需合併邏輯或調整路由,重定向會丟失 POST 數據 (需 Session 閃存傳遞提示)。

方案三:IP/用戶維度限流 (Redis/文件/數據庫) —— 針對刷票/暴力 API

針對「同一用戶/IP 短時間大量請求」,引入滑動窗口/令牌桶限流器。

<?php
// 依賴 Redis (推薦) 或本地文件/數據庫
class RateLimiter
{
    private \Redis $redis;
    public function __construct(\Redis $redis) { $this->redis = $redis; }

    /**
     * @param string $key 限流鍵,如 "vote:ip:192.168.1.1" 或 "api:user:123"
     * @param int $maxRequests 時間窗內最大請求數
     * @param int $windowSeconds 時間窗秒數
     * @return bool true=允許 false=拒絕
     */
    public function allow(string $key, int $maxRequests, int $windowSeconds): bool
    {
        $now = (int)(microtime(true) * 1000); // 毫秒時間戳
        $windowStart = $now - $windowSeconds * 1000;
        $redisKey = "ratelimit:{$key}";

        $pipe = $this->redis->multi();
        // 1. 移除窗口外的舊請求記錄
        $pipe->zRemRangeByScore($redisKey, 0, $windowStart);
        // 2. 統計當前窗口請求數
        $pipe->zCard($redisKey);
        // 3. 執行管道
        $results = $pipe->exec();
        $currentCount = $results[1];

        if ($currentCount >= $maxRequests) {
            return false; // 觸發限流
        }
        // 4. 記錄本次請求 (score=時間戳, member=唯一ID防重複)
        $this->redis->zAdd($redisKey, $now, $now . ':' . random_bytes(8));
        // 5. 設置過期時間 (窗口結束後自動清理)
        $this->redis->expire($redisKey, $windowSeconds + 1);
        return true;
    }
}

// === 使用示例:投票介面限流 ===
$redis = new Redis(); $redis->connect('127.0.0.1', 6379);
$limiter = new RateLimiter($redis);

$ip = $_SERVER['REMOTE_ADDR'];
$userId = $_SESSION['user_id'] ?? 0;
$key = "vote:ip:{$ip}:user:{$userId}";

// 每用戶/IP 每分鐘最多 5 次投票
if (!$limiter->allow($key, 5, 60)) {
    http_response_code(429);
    die(json_encode(['code' => 429, 'msg' => '操作太頻繁,請稍後再試']));
}
// 繼續業務邏輯...

方案對比與組合建議

場景推薦方案核心作用
普通表單提交 (註冊、登入、發帖)Token + PRG防重複提交、防 CSRF
高價值操作 (支付、扣款、改密)Token + PRG + 短信/郵箱二次驗證多重保障
投票/點贊/評論等高頻交互Token + 限流 (IP+用戶雙維度)防刷票、防刷單
開放 API / Webhook 回調簽名驗證 + 限流 + 冪等鍵防重放、防濫用
頁面惡意刷新 (F5/CC 攻擊)Nginx limit_req + PHP 限流 + 靜態化應用層前移攔截

避坑指南

  1. Session 依賴 Cookie:無 Cookie 用戶 (隱私模式、爬蟲) Token 失效,需結合 IP/設備指紋降級策略。
  2. Token 洩露風險:URL 參數攜帶 Token 會泄露至 Referer/日誌,**優先用表單隱藏域或 HTTP Header 傳遞**。
  3. 分佈式環境 Session 共享:多機部署必須使用 Redis/Memcached 做 Session 存儲,否則 Token 驗證失效。
  4. 限流鍵設計:單純 IP 限流易誤傷 NAT 共享出口用戶,**IP + 用戶ID + 設備指紋 多維組合**更精準。
  5. 前端配合:提交按鈕點擊後即 disabled、顯示 Loading,減少無效請求發送。

沒有銀彈,只有分層防禦。Token 驗證守住業務正確性,PRG 模式優化用戶體驗,限流器保護系統可用性——三者結合,構建起 PHP 應用防刷、防重的鋼鐵長城。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved