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

PHP 論壇發文頁面:表單驗證、防重複提交與身分檢查

     編輯:PHP 綜合知識

功能概覽

post.php 是論壇的發文介面,提供標題、內容輸入欄位,整合 JavaScript 前端驗證(必填檢查、防重複送出),未登入使用者需輸入帳號密碼,送出後導向 okey.php 處理。

頁面結構與關鍵程式碼

1. 使用者狀態判斷(Cookie 解析)

<?php
include "linkfox.inc.php";
include "info.inc.php";

$tem = $_COOKIE['FlyFoxNet'] ?? '';
$temp = explode('|', $tem);
$cookiem = $temp[0] ?? '';
$useinfo = cuser($cookiem, $action);

$usename = ($useinfo[2] === '游客') ? 1 : 0; // 1=需驗證身分, 0=已登入
?>

2. JavaScript 表單驗證與防重複提交

<script>
var submitcount = 0;

function checkSubmit() {
    if (submitcount === 0) {
        submitcount++;
        return true;
    } else {
        alert('帖子已成功提交了,別急,等等!');
        return false;
    }
}

function check_com() {
    if (document.form.title.value.length === 0) {
        submitcount--;
        alert('主題不能為空!快填上吧!');
        return false;
    }
    if (document.form.message.value.length === 0) {
        submitcount--;
        alert('OH!SORRY!加貼內容不能為空!');
        return false;
    }
    return true;
}
</script>

缺陷:submitcount 為全域變數,頁面重新整理即失效;無法防禦繞過 JS 的惡意請求。伺服端必須實作 CSRF Token 與冪等性檢查。

3. 動態表單:未登入顯示帳號密碼欄位

<form name="form" method="post" onSubmit="return check_com()" ACTION="okey.php">
<?php if ($usename == 1): ?>
    用戶名:<input type="text" name="username" maxlength="8" size="20">
    密 碼:<input type="password" name="userpass" size="20" maxlength="8">
<?php endif; ?>

主 題:<input type="text" name="title" size="40" maxlength="20"><br>
帖子內容<br>
<textarea name="message" wrap="VIRTUAL" cols="75" rows="10"></textarea><br>
<input type="submit" value="好了!提交" onClick="return checkSubmit()">
<input type="reset" value="擦了!重寫">
</form>

4. 內嵌樣式與舊式寫法

大量使用 style="BACKGROUND-COLOR: #ffffff; BORDER-BOTTOM: #000000 1px dashed;..." 內嵌樣式,建議抽離至 CSS 檔案,並使用類別管理。

安全性與使用者體驗問題

  1. 無 CSRF 保護:表單缺乏 Token,易遭跨站請求偽造攻擊。
  2. 密碼明文傳輸:未使用 HTTPS,帳號密碼以明文 POST 送出。
  3. 前端驗證可繞過:JavaScript 驗證僅為使用者體驗,伺服端必須重新驗證。
  4. 最大長度限制過短:標題 20 字、內容無限制但前端 cols/rows 固定,不利行動裝置。
  5. 錯誤訊息不友善:alert() 阻斷流程,現代建議改用非阻斷的 Toast/Inline 提示。

現代化重構建議

1. 後端驗證(PHP 8+ / PDO)

<?php
// post_handler.php
session_start();
require_once 'db.php';
require_once 'csrf.php';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    exit('Method Not Allowed');
}

if (!validate_csrf_token($_POST['csrf_token'] ?? '')) {
    http_response_code(403);
    exit('Invalid CSRF token');
}

$title = trim($_POST['title'] ?? '');
$content = trim($_POST['message'] ?? '');
$errors = [];

if ($title === '') $errors[] = '主題不可為空';
if (mb_strlen($title) > 100) $errors[] = '主題不得超過 100 字';
if ($content === '') $errors[] = '內容不可為空';
if (mb_strlen($content) > 50000) $errors[] = '內容過長';

if ($errors) {
    // 回傳錯誤給前端(JSON 或重導帶參數)
    $_SESSION['flash_errors'] = $errors;
    header('Location: post.php');
    exit;
}

// 使用者身分解析
$userId = $_SESSION['user_id'] ?? null;
$isGuest = $userId === null;

// 防重複發文:同用戶、同標題、同內容、短時間內
$stmt = $pdo->prepare("
    SELECT 1 FROM topics 
    WHERE user_id = ? AND title = ? AND content = ? 
    AND created_at > DATE_SUB(NOW(), INTERVAL 30 SECOND)
");
$stmt->execute([$userId, $title, $content]);
if ($stmt->fetchColumn()) {
    $_SESSION['flash_errors'] = ['請勿重複發文'];
    header('Location: post.php');
    exit;
}

// 寫入資料庫
$pdo->beginTransaction();
try {
    $stmt = $pdo->prepare("
        INSERT INTO topics (user_id, title, content, ip, is_guest, guest_name)
        VALUES (?, ?, ?, ?, ?, ?)
    ");
    $stmt->execute([
        $userId,
        $title,
        $content,
        $_SERVER['REMOTE_ADDR'],
        $isGuest ? 1 : 0,
        $isGuest ? ($_POST['guest_name'] ?? '匿名') : null
    ]);
    $pdo->commit();
    header('Location: topic.php?id=' . $pdo->lastInsertId());
} catch (Exception $e) {
    $pdo->rollBack();
    throw $e;
}
?>

2. 前端表單(Vue 3 Composition API 範例)

<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'

const form = reactive({
  title: '',
  content: '',
  username: '',
  password: '',
  guestName: ''
})
const errors = ref([])
const submitting = ref(false)
const router = useRouter()

const submit = async () => {
  errors.value = []
  if (!form.title.trim()) errors.value.push('主題不可為空')
  if (!form.content.trim()) errors.value.push('內容不可為空')
  if (errors.value.length) return

  submitting.value = true
  try {
    const formData = new FormData()
    formData.append('title', form.title)
    formData.append('message', form.content)
    formData.append('csrf_token', getCsrfToken()) // 從 meta tag 或 cookie 取得
    if (isGuest.value) {
      formData.append('username', form.username)
      formData.append('userpass', form.password)
    }
    const res = await fetch('post_handler.php', { method: 'POST', body: formData })
    if (res.redirected) {
      router.push(res.url)
    } else {
      const data = await res.json()
      errors.value = data.errors || ['發文失敗']
    }
  } finally {
    submitting.value = false
  }
}
</script>

<template>
  <form @submit.prevent="submit" class="post-form">
    <div v-if="errors.length" class="errors">
      <ul><li v-for="e in errors">{{ e }}</li></ul>
    </div>
    
    <div v-if="isGuest" class="auth-fields">
      <input v-model="form.username" placeholder="用戶名" maxlength="20">
      <input v-model="form.password" type="password" placeholder="密碼" maxlength="20">
    </div>
    
    <input v-model="form.title" placeholder="主題 (最多 100 字)" maxlength="100">
    <textarea v-model="form.content" placeholder="內容" rows="12"></textarea>
    
    <button :disabled="submitting" type="submit">
      {{ submitting ? '發文中...' : '發布主題' }}
    </button>
  </form>
</template>

總結

post.php 實作了基本的發文流程與前端攔截,但缺乏伺服端驗證、CSRF 保護、HTTPS 強制等現代 Web 安全基線。重構時應採用「前端體驗優化、後端安全把關」的分層策略,並引入 CSRF Token、速率限制、內容過濾(XSS 防護)、冪等性鍵等機制,確保發文功能在高併發與惡意攻擊下依然穩健。

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