在PHP项目开发中,表单是用户与系统交互的核心入口,仅依靠前端JavaScript验证无法保障数据安全,因为用户可以通过禁用脚本、直接构造请求等方式绕过前端校验,因此服务端验证和提交拦截是必不可少的环节。

服务端验证的核心作用
服务端验证是在数据到达服务器后、进入业务逻辑处理前进行的校验,主要作用包括三个方面:
- 保障数据合法性:检查提交的数据是否符合业务要求的格式、范围,避免脏数据进入数据库
- 防范安全风险:拦截SQL注入、XSS攻击等恶意构造的请求数据,降低系统被攻击的概率
- 补充前端校验不足:即使前端校验失效,服务端校验也能作为最后一道防线保障数据质量
基础表单与服务端验证实现
首先我们创建一个简单的用户注册表单,表单提交到同页面的PHP处理逻辑中:
<form method="post" action="">
<div>
<label>用户名:</label>
<input type="text" name="username" value="">
</div>
<div>
<label>邮箱:</label>
<input type="email" name="email" value="">
</div>
<div>
<label>密码:</label>
<input type="password" name="password" value="">
</div>
<div>
<label>确认密码:</label>
<input type="password" name="confirm_password" value="">
</div>
<input type="hidden" name="form_token" value="<?php echo $_SESSION['form_token'] ?? ''; ?>">
<button type="submit">提交注册</button>
</form>
接下来编写服务端验证的核心逻辑,首先需要在页面加载时生成表单令牌,用于后续提交拦截:
<?php
session_start();
// 生成表单令牌,防止重复提交和CSRF攻击
if (!isset($_SESSION['form_token'])) {
$_SESSION['form_token'] = bin2hex(random_bytes(32));
}
// 初始化错误数组
$errors = [];
// 仅处理POST请求
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 第一步:提交拦截校验
// 1. 检查请求方法是否为POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$errors[] = '非法请求方式';
}
// 2. 检查表单令牌是否匹配
if (!isset($_POST['form_token']) || $_POST['form_token'] !== $_SESSION['form_token']) {
$errors[] = '表单令牌无效,请刷新页面后重试';
}
// 3. 检查是否为重复提交(令牌使用后失效)
if (isset($_SESSION['form_token_used']) && $_SESSION['form_token_used'] === true) {
$errors[] = '请勿重复提交表单';
}
// 如果拦截校验通过,进行数据合法性验证
if (empty($errors)) {
// 获取并过滤提交的数据
$username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$password = $_POST['password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
// 用户名验证:必填,长度3-20位,仅允许字母数字下划线
if (empty($username)) {
$errors[] = '用户名不能为空';
} elseif (strlen($username) < 3 || strlen($username) > 20) {
$errors[] = '用户名长度需要在3-20位之间';
} elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
$errors[] = '用户名仅允许包含字母、数字和下划线';
}
// 邮箱验证:必填,格式合法
if (empty($email)) {
$errors[] = '邮箱不能为空';
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = '邮箱格式不正确';
}
// 密码验证:必填,长度至少6位,两次输入一致
if (empty($password)) {
$errors[] = '密码不能为空';
} elseif (strlen($password) < 6) {
$errors[] = '密码长度不能少于6位';
}
if ($password !== $confirmPassword) {
$errors[] = '两次输入的密码不一致';
}
// 所有验证通过,处理业务逻辑
if (empty($errors)) {
// 标记令牌已使用,防止重复提交
$_SESSION['form_token_used'] = true;
// 这里可以编写入库、发送邮件等业务逻辑
echo '表单提交成功,数据验证通过';
// 处理完成后可以重定向或者生成新的令牌
unset($_SESSION['form_token']);
unset($_SESSION['form_token_used']);
}
}
}
?>
验证结果展示
如果验证过程中出现错误,我们需要将错误信息展示给用户,在表单上方添加错误输出逻辑:
<?php
// 输出错误信息
if (!empty($errors)) {
echo '<div class="error-box">';
echo '<h3>提交失败,请修正以下问题:</h3>';
echo '<ul>';
foreach ($errors as $error) {
echo '<li>' . htmlspecialchars($error) . '</li>';
}
echo '</ul>';
echo '</div>';
}
?>
常见验证场景扩展
除了上述基础验证,实际开发中还会遇到更多场景的校验需求:
数字范围验证
比如年龄字段需要限制在18-60之间,可以使用如下逻辑:
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT);
if ($age === false || $age < 18 || $age > 60) {
$errors[] = '年龄需要是18到60之间的整数';
}
文件上传验证
如果表单包含文件上传,需要验证文件类型、大小等信息:
if (isset($_FILES['avatar'])) {
$allowedTypes = ['image/jpeg', 'image/png'];
$maxSize = 2 * 1024 * 1024; // 2MB
if (!in_array($_FILES['avatar']['type'], $allowedTypes)) {
$errors[] = '头像仅支持JPG和PNG格式';
}
if ($_FILES['avatar']['size'] > $maxSize) {
$errors[] = '头像大小不能超过2MB';
}
}
注意事项
- 所有输出到页面的用户输入数据都需要用
htmlspecialchars转义,避免XSS攻击 - 表单令牌需要每次提交后更新,避免令牌被复用导致安全问题
- 验证规则需要根据实际业务需求调整,不要过度校验也不要遗漏必要校验项
- 对于敏感数据如密码,不要直接存储明文,需要使用
password_hash函数加密后存储