PHP 表单怎么处理?GET、POST 和表单验证的完整基础
图片说明:示意图 图片来源:Public Domain(公有领域)
PHP 表单看起来是Web开发里最基础的功能,真正进入生产环境以后,却往往成为安全问题和业务错误最集中的地方之一。用户填写的数据从浏览器进入HTTP请求,再由PHP接收、验证、处理,最后可能进入数据库、HTML页面、邮件系统或者文件存储。任何一个环节处理不当,都可能产生SQL注入、跨站脚本攻击、参数篡改、文件上传漏洞或者业务逻辑绕过。
因此,PHP表单处理不能简单理解成“使用$_POST接收数据,然后插入数据库”。更合理的做法是把请求解析、输入验证、业务规则检查、数据库操作和输出编码分别处理。每一层解决不同的问题,不能用一个函数包办所有安全工作。
GET和POST到底有什么区别
GET和POST最明显的区别是数据传输的位置。GET请求通常把参数编码到URL的查询字符串中,例如搜索页面可能出现类似search.php?q=php的请求。PHP可以通过$_GET['q']读取参数。
GET非常适合查询、筛选、分页、排序以及其他不会改变服务器状态的操作。例如新闻网站的搜索、分类筛选和分页链接,都很适合使用GET。由于参数位于URL中,用户可以收藏、复制和分享完整地址,搜索引擎也更容易理解这种资源定位方式。
POST则通常把表单数据放在HTTP请求体中,PHP通过$_POST读取普通表单字段。注册、登录、创建文章、修改账户信息以及其他会改变服务器状态的操作,通常使用POST。
但需要纠正一个经常出现的误解:POST并不意味着数据天然更安全。POST数据通常不会直接显示在浏览器地址栏里,但它仍然可能出现在浏览器开发者工具、代理服务器、应用日志或者其他基础设施中。如果没有HTTPS,POST中的密码同样可能在传输过程中被窃取。
真正保护传输数据的是HTTPS,而不是GET或者POST本身。
GET同样没有所谓固定的“2048字符绝对限制”。URL能够使用多长,实际上取决于浏览器、Web服务器、反向代理、应用服务器以及中间网络设备等多个环节。实际开发中应该避免把大量数据塞进URL,但不应该把2048字符当成PHP或者HTTP协议规定的统一上限。
PHP接收表单数据时不要假设字段一定存在
最简单的写法是:
$username = $_POST['username'];
但生产环境不应该假定用户一定按照页面提供的表单提交请求。用户可以直接访问PHP地址,也可以手工构造HTTP请求,甚至完全不经过网页上的HTML表单。
更稳妥的方式是明确读取参数,并处理字段缺失情况:
$username = trim($_POST['username'] ?? '');
$email = trim($_POST['email'] ?? '');
如果只允许POST提交,还应该检查HTTP请求方法:
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit('Method Not Allowed');
}
这一步解决的是请求方式和字段存在性问题,并不能代替业务验证。
例如一个注册接口即使收到了username字段,也不能因此认为用户名合法。还需要检查长度、允许字符、重复状态以及其他业务规则。
前端验证只能改善体验,不能承担安全责任
HTML5提供了required、minlength、maxlength、type="email"、pattern等验证能力。例如:
这些功能能够让用户在提交之前立即发现一些明显错误,减少无意义的服务器请求,因此对于用户体验非常有价值。
但浏览器端验证完全不能作为安全边界。用户可以关闭JavaScript、修改HTML、直接发送HTTP请求,或者使用Postman、curl等工具绕过网页。
因此服务器端必须重新验证所有重要字段。
例如:
$email = trim($_POST['email'] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
exit('Invalid email address');
}
对于用户名、金额、日期、权限、订单状态等数据,则应该根据业务规则编写明确的验证逻辑。
不要再使用FILTER_SANITIZE_STRING作为通用清理方案
原稿使用FILTER_SANITIZE_STRING作为表单数据清理工具,这种写法已经过时。该过滤器从PHP 8.1开始被废弃,不应该在新的PHP项目中继续把它当成通用输入清洗方案。
更重要的是,“验证”和“清理”本来就是两个不同概念。
例如年龄字段应该验证它是不是合法整数,而不是简单把输入字符串“清洗干净”。
$age = filter_input(
INPUT_POST,
'age',
FILTER_VALIDATE_INT
);
if ($age === false || $age === null || $age < 0 || $age > 120) {
exit('Invalid age');
}
用户名则可以根据业务要求限制长度和字符集。
金额字段还需要结合精度、货币单位和业务范围验证,而不是简单调用一个所谓“安全过滤函数”。
输入数据到底应该如何处理,取决于它进入系统之后要承担什么角色。
htmlspecialchars不是输入验证函数
PHP开发中另一个非常重要的概念,是不要把htmlspecialchars()当成万能安全函数。
它主要用于HTML上下文中的输出编码。
例如用户提交:
在HTML页面中直接输出:
echo $username;
可能造成XSS风险。
如果这是一个HTML文本节点,则可以:
echo htmlspecialchars(
$username,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
输出后,浏览器不会把用户输入中的HTML标签当成真正的HTML元素执行。
这里还有一个常见错误。htmlspecialchars()并不是简单把字符串变成所谓“全部HTML实体”。例如普通中文不会因为调用这个函数就自动变成数字实体。它主要处理HTML语境下具有特殊意义的字符,例如&、<、>以及引号。
同时,应该避免把经过HTML编码的数据提前保存到数据库。
更合理的做法通常是数据库保存原始业务数据,在输出到HTML时根据具体上下文进行编码。因为同一份数据未来可能进入HTML正文、HTML属性、JavaScript、JSON、URL或者其他上下文,而每种上下文需要不同的安全处理方法。
SQL注入应该交给预处理语句解决
如果用户提交:
$username = $_POST['username'] ?? '';
然后直接拼接SQL:
$sql = "SELECT * FROM users WHERE username = '$username'";
就是典型的SQL注入风险。
PDO预处理语句可以把SQL结构和用户参数分开:
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE username = ?'
);
$stmt->execute([$username]);
插入数据时同样如此:
$stmt = $pdo->prepare(
'INSERT INTO users (username, email)
VALUES (?, ?)'
);
$stmt->execute([$username, $email]);
这里需要强调,htmlspecialchars()不能防止SQL注入。两者解决的是完全不同的问题。
SQL注入属于数据库查询结构安全问题;XSS则主要涉及浏览器解释HTML、JavaScript和相关输出上下文。把所有用户输入统一调用某一个“过滤函数”并不能解决这些问题。
CSRF是表单系统经常遗漏的一层
如果网站存在登录状态,攻击者可能诱导已经登录的用户访问恶意页面,然后利用浏览器自动携带Cookie向目标网站提交请求。
因此,对于修改密码、修改账户资料、删除内容、创建订单等改变服务器状态的操作,除了检查POST和业务参数,还应该考虑CSRF防护。
典型做法是在服务器端为用户会话生成不可预测的CSRF Token,并把Token放进表单:
服务器收到请求后验证Token是否与当前会话匹配。
现代系统还可以结合Cookie的SameSite策略、Origin检查以及其他请求来源控制形成多层防护。
需要注意,CSRF Token不能代替身份认证和权限检查。用户已经登录并不意味着他有权限修改任意资源。
权限验证比表单字段验证更重要
假设后台存在:
POST /admin/delete.php?id=100
服务器不能因为id是一个合法整数,就允许请求继续执行。
还需要确认当前用户是否有删除ID为100这条记录的权限。
因此,表单安全至少包含几个不同层次:字段格式是否正确,数据是否符合业务规则,当前用户是否经过身份认证,以及当前用户是否拥有执行这项操作的权限。
尤其是后台管理系统,不能相信隐藏字段:
隐藏字段只是浏览器中的普通数据。用户完全可以修改成:
user_id=101
服务器必须根据当前登录身份和数据库中的授权关系重新确认操作对象。
文件上传不能只检查扩展名
文件上传是PHP表单中风险较高的一部分。
HTML表单需要使用:
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP