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

PHP Token 防重複提交機制改進版:解決 Base64 加號編碼問題

     編輯:PHP 綜合知識

問題背景

Token 經 base64_encode() 後包含 +、/、=。透過 URL GET 傳遞時,+ 會被解析為空格,導致解碼失敗。改進版將 + 替換為 _ (URL 安全字元),傳輸前再還原。

GEncrypt 加解密核心

<?php
class GEncrypt {
    protected static function keyED($txt, $key) {
        $key = md5($key);
        $ctr = 0; $tmp = '';
        for ($i = 0; $i < strlen($txt); $i++) {
            if ($ctr == strlen($key)) $ctr = 0;
            $tmp .= $txt[$i] ^ $key[$ctr++];
        }
        return $tmp;
    }

    public static function encrypt($txt, $key) {
        $encrypt_key = md5((float)date('YmdHis') + rand(1e16, 99999999999999999) . rand(100000, 999999));
        $ctr = 0; $tmp = '';
        for ($i = 0; $i < strlen($txt); $i++) {
            if ($ctr == strlen($encrypt_key)) $ctr = 0;
            $tmp .= $encrypt_key[$ctr] . ($txt[$i] ^ $encrypt_key[$ctr++]);
        }
        // 關鍵:將 + 替換為 _
        return preg_replace('/\+/s', '_', base64_encode(self::keyED($tmp, $key)));
    }

    public static function decrypt($txt, $key) {
        if ($txt === '') return false;
        // 還原 _ 為 +
        $txt = self::keyED(base64_decode(preg_replace('/_/s', '+', $txt)), $key);
        $tmp = '';
        for ($i = 0; $i < strlen($txt); $i++) {
            $md5 = $txt[$i++];
            $tmp .= $txt[$i] ^ $md5;
        }
        return $tmp;
    }
}
?>

GToken Token 管理

<?php
class GToken {
    public static function getTokens() {
        $tokens = $_SESSION[GConfig::SSN_KEY_TOKEN] ?? [];
        return is_array($tokens) ? $tokens : [];
    }

    public static function newToken($formName, $key = GConfig::ENCRYPT_KEY) {
        return GEncrypt::encrypt($formName . session_id(), $key);
    }

    public static function dropToken($token) {
        $tokens = self::getTokens();
        $tokens[] = $token;
        GSession::set(GConfig::SESSION_KEY_TOKEN, $tokens);
    }

    public static function isToken($token, $formName, $fromCheck = false, $key = GConfig::ENCRYPT_KEY) {
        if (empty($token)) return false;
        $tokens = self::getTokens();
        if (in_array($token, $tokens, true)) return false; // 已使用

        $source = GEncrypt::decrypt($token, $key);
        if ($fromCheck) {
            return $source === $formName . session_id();
        }
        return strpos($source, $formName) === 0;
    }

    public static function getTokenKey($token, $key = GConfig::ENCRYPT_KEY) {
        if (empty($token)) return false;
        $source = GEncrypt::decrypt($token, $key);
        return $source !== '' ? str_replace(session_id(), '', $source) : false;
    }

    // Smarty 外掛支援
    public function newTokenForSmarty($params) {
        extract($params);
        return self::newToken($form ?? '');
    }
}
?>

使用流程

  1. 表單頁:<input type="hidden" name="token" value="">
  2. 處理頁:
    if (GToken::isToken($_POST['token'], 'post_form')) {
        // 驗證通過,處理業務邏輯
        GToken::dropToken($_POST['token']); // 標記已用
    } else {
        // Token 無效或重複提交
    }
    

關鍵改進點

  • URL 安全:+ → _ 避免 GET 傳輸語義衝突。
  • 一次性:Session 陣列記錄已用 Token,防重複提交。
  • 綁定 Session:fromCheck=true 可驗證 Token 內嵌的 session_id,防跨站盜用。
  • 時間戳+隨機數:加密金鑰含時間與大範圍隨機數,抗暴力破解。

結語

改進版 Token 機制解決 Base64 URL 傳輸編碼問題,並提供一次性、Session 綁定、表單隔離等多層防禦,適合表單密集型應用防重複提交與 CSRF 保護。

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