表单验证是Web开发中至关重要的环节,它确保用户输入的数据符合预期格式,同时防止恶意攻击。本章节将详细介绍PHP表单验证的各种技术和方法。
在处理PHP表单时,安全性是首要考虑因素。不恰当的验证可能导致SQL注入、跨站脚本攻击(XSS)、跨站请求伪造(CSRF)等安全问题。
一个完整的表单验证系统应该包含以下字段验证:
| 字段 | 验证规则 |
|---|---|
| 姓名 (Name) | 必须填写,只能包含字母和空格 |
| 邮箱 (E-mail) | 必须填写,必须是有效的电子邮件格式(包含'@'和'.') |
| 网址 (Website) | 可选,如果填写必须包含有效的URL格式 |
| 评论 (Comment) | 必须填写,多行文本输入 |
| 性别 (Gender) | 必须选择一个 |
完整的HTML表单代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户信息表单</title>
<style>
.error { color: red; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input[type="text"], input[type="email"], textarea {
width: 100%; max-width: 400px; padding: 8px;
border: 1px solid #ddd; border-radius: 4px;
}
</style>
</head>
<body>
<h2>用户信息表单</h2>
<p><span class="error">* 必填字段</span></p>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<div class="form-group">
<label for="name">姓名: <span class="error">*</span></label>
<input type="text" id="name" name="name" value="<?php echo $name ?? ''; ?>">
<span class="error"><?php echo $nameErr ?? ''; ?></span>
</div>
<div class="form-group">
<label for="email">邮箱: <span class="error">*</span></label>
<input type="text" id="email" name="email" value="<?php echo $email ?? ''; ?>">
<span class="error"><?php echo $emailErr ?? ''; ?></span>
</div>
<div class="form-group">
<label for="website">网址:</label>
<input type="text" id="website" name="website" value="<?php echo $website ?? ''; ?>">
<span class="error"><?php echo $websiteErr ?? ''; ?></span>
</div>
<div class="form-group">
<label for="comment">评论: <span class="error">*</span></label>
<textarea id="comment" name="comment" rows="5" cols="40"><?php echo $comment ?? ''; ?></textarea>
<span class="error"><?php echo $commentErr ?? ''; ?></span>
</div>
<div class="form-group">
<label>性别: <span class="error">*</span></label>
<input type="radio" id="female" name="gender" value="female" <?php echo (($gender ?? '') == "female") ? "checked" : ""; ?>>
<label for="female">女</label>
<input type="radio" id="male" name="gender" value="male" <?php echo (($gender ?? '') == "male") ? "checked" : ""; ?>>
<label for="male">男</label>
<input type="radio" id="other" name="gender" value="other" <?php echo (($gender ?? '') == "other") ? "checked" : ""; ?>>
<label for="other">其他</label>
<span class="error"><?php echo $genderErr ?? ''; ?></span>
</div>
<div class="form-group">
<input type="submit" name="submit" value="提交">
</div>
</form>
</body>
</html>
XSS(跨站脚本攻击)是常见的Web安全漏洞。恶意攻击者往Web页面里插入恶意JavaScript代码,当用户浏览该页时,嵌入的代码会被执行,从而达到攻击目的。
不安全的写法:
<!-- 存在XSS漏洞 -->
<form method="post" action="<?php echo $_SERVER["PHP_SELF"]; ?>">
攻击者可以构造这样的URL:
https://www.example.com/test_form.php/"><script>alert('XSS攻击')</script>
这会导致生成的HTML变成:
<form method="post" action="test_form.php/"><script>alert('XSS攻击')</script>">
安全的写法:
<!-- 使用htmlspecialchars()函数转义 -->
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
现在恶意代码会被转义为:
<form method="post" action="test_form.php/"><script>alert('XSS攻击')</script>">
<?php
// 定义变量并设置为空值
$name = $email = $gender = $comment = $website = "";
$nameErr = $emailErr = $genderErr = $commentErr = $websiteErr = "";
// 检查表单是否提交
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 验证姓名
if (empty($_POST["name"])) {
$nameErr = "姓名是必填的";
} else {
$name = test_input($_POST["name"]);
// 检查姓名是否只包含字母和空格
if (!preg_match("/^[a-zA-Z\x{4e00}-\x{9fa5} ]+$/u", $name)) {
$nameErr = "只允许字母、中文和空格";
}
}
// 验证邮箱
if (empty($_POST["email"])) {
$emailErr = "邮箱是必填的";
} else {
$email = test_input($_POST["email"]);
// 检查邮箱格式
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$emailErr = "无效的邮箱格式";
}
}
// 验证网址
if (!empty($_POST["website"])) {
$website = test_input($_POST["website"]);
// 检查URL地址语法是否有效
if (!preg_match("/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i", $website)) {
$websiteErr = "无效的URL";
}
}
// 验证评论
if (empty($_POST["comment"])) {
$commentErr = "评论是必填的";
} else {
$comment = test_input($_POST["comment"]);
}
// 验证性别
if (empty($_POST["gender"])) {
$genderErr = "性别是必选的";
} else {
$gender = test_input($_POST["gender"]);
}
}
/**
* 数据清理函数
* 1. 去除首尾空白字符
* 2. 去除反斜杠
* 3. 转换特殊字符为HTML实体
*/
function test_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
return $data;
}
// 如果验证通过,显示成功消息
if ($_SERVER["REQUEST_METHOD"] == "POST" && empty($nameErr) && empty($emailErr) && empty($websiteErr) && empty($commentErr) && empty($genderErr)) {
echo "<div class='success'>";
echo "<h2>表单提交成功!</h2>";
echo "<p><strong>姓名:</strong> " . $name . "</p>";
echo "<p><strong>邮箱:</strong> " . $email . "</p>";
if (!empty($website)) {
echo "<p><strong>网址:</strong> " . $website . "</p>";
}
echo "<p><strong>评论:</strong> " . $comment . "</p>";
echo "<p><strong>性别:</strong> " . $gender . "</p>";
echo "</div>";
// 在实际应用中,这里可以保存到数据库
// save_to_database($name, $email, $website, $comment, $gender);
}
?>
1. 使用filter_var函数进行验证
<?php
// 使用PHP内置过滤器进行验证
function advanced_validation($data) {
$errors = [];
// 邮箱验证
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = "无效的邮箱地址";
}
// URL验证
if (!empty($data['website']) && !filter_var($data['website'], FILTER_VALIDATE_URL)) {
$errors['website'] = "无效的URL地址";
}
// IP地址验证
if (!empty($data['ip']) && !filter_var($data['ip'], FILTER_VALIDATE_IP)) {
$errors['ip'] = "无效的IP地址";
}
// 整数验证
if (!empty($data['age']) && !filter_var($data['age'], FILTER_VALIDATE_INT,
array("options" => array("min_range" => 1, "max_range" => 120)))) {
$errors['age'] = "年龄必须是1-120之间的整数";
}
return $errors;
}
?>
2. 自定义验证规则
<?php
class FormValidator {
private $errors = [];
public function validate($rules, $data) {
foreach ($rules as $field => $rule) {
$value = $data[$field] ?? '';
// 必填验证
if (in_array('required', $rule) && empty($value)) {
$this->errors[$field] = "{$field}是必填的";
continue;
}
// 如果字段为空且不是必填,跳过其他验证
if (empty($value)) continue;
// 邮箱验证
if (in_array('email', $rule) && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$this->errors[$field] = "{$field}格式不正确";
}
// URL验证
if (in_array('url', $rule) && !filter_var($value, FILTER_VALIDATE_URL)) {
$this->errors[$field] = "{$field}必须是有效的URL";
}
// 最小长度验证
foreach ($rule as $r) {
if (strpos($r, 'min:') === 0) {
$min = (int) str_replace('min:', '', $r);
if (strlen($value) < $min) {
$this->errors[$field] = "{$field}至少需要{$min}个字符";
}
}
// 最大长度验证
if (strpos($r, 'max:') === 0) {
$max = (int) str_replace('max:', '', $r);
if (strlen($value) > $max) {
$this->errors[$field] = "{$field}不能超过{$max}个字符";
}
}
}
}
return empty($this->errors);
}
public function getErrors() {
return $this->errors;
}
}
// 使用示例
$validator = new FormValidator();
$rules = [
'name' => ['required', 'min:2', 'max:50'],
'email' => ['required', 'email'],
'website' => ['url'],
'age' => ['required', 'min:1', 'max:3']
];
if ($validator->validate($rules, $_POST)) {
echo "验证通过";
} else {
print_r($validator->getErrors());
}
?>
1. SQL注入防护
<?php
// 不安全的写法 - 容易受到SQL注入攻击
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
// 安全的写法 - 使用预处理语句
function safe_login($username, $password) {
try {
$pdo = new PDO("mysql:host=localhost;dbname=test", "username", "password");
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$username, $password]);
return $stmt->fetch();
} catch (PDOException $e) {
return false;
}
}
?>
2. CSRF防护
<?php
// 生成CSRF令牌
session_start();
function generate_csrf_token() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// 验证CSRF令牌
function validate_csrf_token($token) {
return isset($_SESSION['csrf_token']) &&
hash_equals($_SESSION['csrf_token'], $token);
}
// 在表单中添加CSRF令牌
$csrf_token = generate_csrf_token();
echo "<input type='hidden' name='csrf_token' value='$csrf_token'>";
// 处理表单时验证
if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (!validate_csrf_token($_POST['csrf_token'] ?? '')) {
die("CSRF令牌验证失败");
}
// 处理表单数据...
}
?>
<?php
function validate_file_upload($file) {
$errors = [];
// 检查上传错误
if ($file['error'] !== UPLOAD_ERR_OK) {
$errors[] = "文件上传失败,错误代码: " . $file['error'];
return $errors;
}
// 检查文件类型
$allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
$file_type = mime_content_type($file['tmp_name']);
if (!in_array($file_type, $allowed_types)) {
$errors[] = "只允许上传JPEG, PNG, GIF图片或PDF文件";
}
// 检查文件大小 (最大2MB)
$max_size = 2 * 1024 * 1024;
if ($file['size'] > $max_size) {
$errors[] = "文件大小不能超过2MB";
}
// 检查文件扩展名
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$file_extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($file_extension, $allowed_extensions)) {
$errors[] = "不支持的文件类型";
}
// 检查文件名安全性
if (preg_match('/[^\w\.\-]/', $file['name'])) {
$errors[] = "文件名包含非法字符";
}
return $errors;
}
// 使用示例
if (isset($_FILES['avatar'])) {
$validation_errors = validate_file_upload($_FILES['avatar']);
if (empty($validation_errors)) {
// 生成安全文件名
$extension = pathinfo($_FILES['avatar']['name'], PATHINFO_EXTENSION);
$safe_filename = uniqid() . '.' . $extension;
$upload_path = 'uploads/' . $safe_filename;
if (move_uploaded_file($_FILES['avatar']['tmp_name'], $upload_path)) {
echo "文件上传成功";
} else {
echo "文件移动失败";
}
} else {
foreach ($validation_errors as $error) {
echo "<div class='error'>$error</div>";
}
}
}
?>
| 安全措施 | 实现方法 | 防护目标 |
|---|---|---|
| 输入验证 | 使用filter_var()、正则表达式 |
确保数据格式正确 |
| 数据清理 | trim()、stripslashes()、htmlspecialchars() |
移除有害字符 |
| XSS防护 | 输出时使用htmlspecialchars() |
防止跨站脚本攻击 |
| SQL注入防护 | 使用预处理语句 | 防止数据库攻击 |
| CSRF防护 | 使用CSRF令牌 | 防止跨站请求伪造 |
| 文件上传安全 | 验证文件类型、大小、重命名 | 防止恶意文件上传 |