滚动新闻 →
Kubernetes 如何处理 AI 训练任务失败,任务重启与训练断点恢复有什么区别 福建女商人贷款500万被判无罪 债务增至1300万 孙艺珍要求玄彬拍爱情戏需获批准?玄彬夫妇青龙奖合体互动引热议 珠海惊传当街砍人 幼童倒卧血泊疑已身亡(慎入/视频) 张升民会见俄防长引关注 董军未现身引猜测 Google Cloud 延迟测试怎么做,用户所在地和服务器区域应该怎样匹配 百方刻碑传后人 河南老中医遗愿成医德传承 MySQL 数据量达到千万级以后,SaaS 数据库应该考虑哪些变化 温哥华市议会开启城市发展新篇章 电脑内存出现故障有哪些表现?蓝屏、死机和无法开机如何区分 延安大学副教授绝笔信曝光 投湖身亡引关注 英国冒险家历时28年徒步环球旅行终返故里 Windows 11 Wi-Fi 怎么连接?保存网络、自动连接和密码管理技巧 许皓鋐首局力克赖均辅 棋王赛先下一城 美国财政赤字为什么会影响加拿大房贷利率?跨境资本如何把华盛顿的债务压力传导到加拿大居民家庭 加拿大科研安全引关注 专家指中企协助学者规避审查 珠峰南坡海拔4410米酒店遭遇严重火灾 现场画面曝光 索马里机场致命事故:保镖被旋转螺旋桨击中身亡 美国财政赤字持续扩大,国债收益率、美元信用与全球资本流动将面临什么变化 MySQL EXPLAIN 怎么看?从执行计划判断查询是否存在性能问题 美日印澳联合声明聚焦经济胁迫 矛头直指中国供应链 白宫副新闻秘书:支持共产议程或面临高昂代价 白宫将接待俄乌代表,梅拉尼娅将主持会谈 中期选举甜蜜促销:出示投票贴纸免费领取甜甜圈 武汉外卖员聚集抗议引爆炸案 伤亡情况待核实 AI芯片巨头集体飙升 苹果财报亮眼股价却下挫 加州选民踊跃投票 期待新州长兑现民生承诺 美国中期选举全面展开 德州参议员之争成为全国焦点 纽约州长霍楚王后区竞选:强调保障民众抗议共产主义权利 美国中期选举进入白热化 国会控制权争夺今夜见分晓 币安63亿美元遭黑客入侵 假面试成入侵突破口 美军突袭伊朗军事重地 川普表态非全面开战 邢台市委书记杨猛被查 当地网民以烟花庆祝 中期选举战火升级 竞选结果仍悬而未决 美国中期选举拉开帷幕 国会权力格局引关注 古代文学中的家庭关系有哪些特点 美国选举日暴雨影响投票 多家企业推出交通优惠 川普现身保守派播客力挺共和党 纽约选民聚焦民生议题 中期选举折射社会关切 传统中医与中国饮食礼仪

PHP 表单验证有哪些原则?不要让浏览器提交的数据直接进入数据库

发布时间: 2026-10-14 12:00:02    最后更新: 2026-11-03 21:13:56    阅读:833  约16 分钟阅读     

PHP 表单验证有哪些原则?不要让浏览器提交的数据直接进入数据库
图片说明:示意图   图片来源:Public Domain(公有领域)
在 PHP 网站开发中,表单验证看起来是一个很基础的问题,实际上却是应用安全的第一道边界。浏览器提交过来的数据,从服务器的角度看都属于不可信输入。无论页面上的 HTML、JavaScript 还是浏览器开发者工具显示了什么,都不能作为服务器端安全判断的依据。 用户可以绕过网页上的 JavaScript,直接向 PHP 接口发送 HTTP 请求,也可以修改隐藏字段、改变价格参数、伪造用户 ID、提交超长字符串,甚至完全不按照网页表单原本设计的格式发送数据。因此,PHP 后端必须重新验证每一个来自客户端的数据。 这里有一个非常重要的原则:验证、规范化、输出编码和数据库安全是四件不同的事情。把它们全部称为“过滤输入”,很容易让开发者建立错误的安全模型。 浏览器提交的数据默认都不可信 例如一个注册表单可能包含: username email password age phone 这些数据进入 PHP 后,不能因为 HTML 表单已经写了 required、maxlength 或 JavaScript 已经验证过,就直接写入数据库。 HTML 的 required 只是浏览器端行为,JavaScript 验证也属于客户端逻辑。攻击者完全可以绕过它们,直接构造 POST 请求。 服务器应该根据业务规则重新检查数据。 例如年龄应该是允许范围内的整数,邮箱应该符合应用接受的邮箱格式,用户名应该符合规定的字符和长度,订单金额不能由客户端随意决定,用户 ID 也不能因为来自隐藏字段就被认为可信。 因此,后端验证的核心不是“把危险字符删掉”,而是判断“这个字段是否符合服务器预期”。 验证和过滤不是一回事 PHP 提供的 filter_var() 很适合处理一些标准格式,例如: FILTER_VALIDATE_EMAIL FILTER_VALIDATE_IP FILTER_VALIDATE_URL 它可以作为验证工具使用,但不能把它理解成万能安全过滤器。 例如邮箱地址可以使用: $email = filter_var($_POST['email'] ?? '', FILTER_VALIDATE_EMAIL); if ($email === false) { // 验证失败 } 对于电话号码、用户名、订单编号等业务字段,则通常需要根据自己的业务规则进行验证。 例如一个订单编号可能要求只能包含大写字母、数字和短横线,这种规则就应该明确写出来,而不是简单地把“不认识的字符”全部删除。 preg_match() 可以用于自定义格式验证,但正则表达式也不应该被滥用。对于特别复杂、可能触发高计算量的正则表达式,还需要考虑回溯和 ReDoS 风险。验证规则应该尽量明确、有限、可预测。 不要用 htmlspecialchars() 来“清洗输入” 这是 PHP 初学者最容易形成的错误习惯之一。 htmlspecialchars() 的主要用途是 HTML 输出编码,而不是数据库输入过滤。 例如数据库中允许保存: Tom & Jerry 这类普通文本完全可能是合法业务数据。 当以后把它输出到 HTML 页面时,才应该根据 HTML 上下文进行编码: echo htmlspecialchars( $name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8' ); 这样浏览器看到的是安全的文本,而不是把用户输入中的 HTML 标签当成页面代码执行。 因此正确的思路是“存储数据”和“输出数据”分开处理。 不要为了防止 XSS,在用户提交时就把所有 <、>、引号等字符永久改掉。 否则数据库里的原始业务数据可能被破坏,后续输出到 JSON、CSV、邮件、纯文本或者其他系统时也会产生新的问题。 SQL 注入应该由预处理语句解决 防止 SQL 注入不能靠删除单引号,也不能靠 htmlspecialchars()。 PHP 使用 PDO 时,应该采用预处理语句: $stmt = $pdo->prepare( 'SELECT * FROM users WHERE email = :email' ); $stmt->execute([ ':email' => $email ]); 用户输入作为参数传递,而不是直接拼接进 SQL 字符串。 错误的做法类似: $sql = "SELECT * FROM users WHERE email = '$email'"; 即使前面已经做过邮箱格式检查,也不能因此把字符串拼接 SQL 当成安全方案。 验证的作用是保证数据符合业务规则;预处理语句的作用是建立 SQL 代码和参数之间的边界。这两个安全机制不能互相替代。 数据库字段本身也应该有约束 应用程序验证并不是数据库安全的最后一层。 例如用户名要求唯一,那么数据库应该建立 UNIQUE 约束;某个字段不允许为空,可以使用 NOT NULL;数值字段应该使用合理的数据类型;外键关系应该根据实际业务建立约束。 原因很简单:今天只有一个 PHP 页面写数据库,不代表半年以后仍然只有一个入口。 未来可能出现 API、后台管理系统、命令行脚本、定时任务、移动端程序或者第三方服务。如果数据完整性完全依赖某一个 PHP 表单验证函数,系统很容易出现“某个入口忘记验证”的问题。 应用层负责业务验证,数据库负责数据完整性,两者应该形成不同层次的防线。 不要相信隐藏字段、价格字段和用户 ID 安全开发中有一条非常实用的经验:客户端提交的业务关键数据,服务器不要直接相信。 例如购物车页面提交: product_id=1001 price=9.99 quantity=1 服务器不能简单地接受 price=9.99 并创建订单。 客户端可以把它改成: price=0.01 正确做法是根据服务器保存的商品 ID重新读取价格,再根据服务器端规则计算订单金额。 用户 ID也是类似的问题。 如果当前登录用户是通过服务器端 Session 或安全认证机制确定的,就不要因为 POST 中出现了: user_id=123 就认为当前请求有权修改用户 123 的资料。 权限判断必须建立在服务器端身份和授权规则之上。 这比单纯讨论“过滤特殊字符”更加重要。 长度限制同样属于安全措施 字符串长度不能无限制地接受。 例如用户名可能限制为几十个字符,标题可能限制为几百个字符,评论可能限制为几千个字符。 PHP 可以先进行长度检查: if (mb_strlen($title, 'UTF-8') > 200) { $errors['title'] = '标题过长'; } 处理中文等多字节字符时,不能机械地把 strlen() 当作“用户看到的字符数”。 如果业务规则要求限制字符数量,通常应该考虑 mb_strlen()。 但也不要简单地使用 substr() 把超长内容截断后直接保存。截断可能破坏 UTF-8 字符、URL、代码、JSON或者其他结构化数据,也可能改变用户提交的数据语义。 更合理的方式通常是拒绝超过业务限制的数据,并向用户返回明确错误。 POST 不是安全机制 很多 PHP 开发者会产生一个误解:GET 不安全,POST 安全。 实际上,POST 只是 HTTP 请求方法,并不自动提供加密或安全保护。 GET 参数通常出现在 URL 中,而 POST 数据通常位于 HTTP 请求体中。两者都可能被客户端构造,都必须进行服务器端验证。 真正保护传输内容的是 HTTPS。 密码绝对不应该因为“POST比较安全”就明文处理。服务器应该通过: password_hash() 生成密码哈希,并使用: password_verify() 进行验证。 数据库里不应该保存用户的明文密码。 同样,CSRF 也不是因为使用 POST 就自动消失。对于会改变服务器状态的请求,例如修改账户、删除文章、提交订单、修改密码等,仍然需要根据应用架构实施 CSRF 防护或采用其他有效的请求来源验证机制。 文件上传需要单独处理 文件上传不能只检查扩展名。 攻击者可以把一个恶意文件命名成: photo.jpg 甚至伪造 MIME 类型。 服务器应该根据具体业务检查文件大小、实际文件类型、扩展名、文件内容以及保存位置,并避免让用户上传的文件直接成为可执行脚本。 上传目录的 Web 服务器执行权限也应该根据实际部署环境进行控制。 对于图片,还可以使用图像处理库重新解码并生成新的文件,而不是盲目相信客户端提供的 MIME 类型。 文件上传属于独立的安全边界,不能简单套用普通文本字段的验证逻辑。 错误信息要对用户友好,对攻击者克制 验证失败时,应该告诉用户下一步该怎么修改。 例如: “请输入有效的邮箱地址。” 比直接显示数据库异常要合适得多。 更不能把 SQL 查询、数据库用户名、服务器路径、PHP 堆栈或者内部文件名直接输出给普通用户。 开发环境可以记录详细异常,生产环境则应该把技术细节写入服务器日志,并向用户返回经过控制的错误信息。 另外,错误信息本身也必须经过正确的输出编码。如果错误信息包含用户提交的内容,就不能直接把原始输入拼接进 HTML。 复杂表单不要只返回第一个错误 原稿中“某个验证失败后立即终止后续验证”的说法也需要修正。 对于复杂表单,更好的用户体验通常是一次完成全部字段检查,把多个错误集中返回。 例如一次提交同时发现邮箱格式错误、密码长度不足、用户名已经存在,就可以分别记录: $errors['email'] = '请输入有效的邮箱地址'; $errors['password'] = '密码长度不足'; $errors['username'] = '用户名已经存在'; 最后判断: if (!empty($errors)) { // 返回表单和错误信息 } 这样用户修改一次就可以重新提交,而不是每次只发现一个问题。 当然,如果某个条件属于流程级致命错误,例如身份认证失败、请求签名错误、CSRF 校验失败或者关键资源不存在,就可以直接终止请求。 因此应该区分“字段验证错误”和“请求级安全错误”。 客户端验证负责体验,服务器验证负责安全 JavaScript 验证非常有价值。 用户输入邮箱时,前端可以立即提示格式错误;密码太短,可以马上显示原因;数量超过库存,也可以即时提示。 这样可以减少无效请求,让页面体验更加流畅。 但这些检查都不能成为服务器最终的安全依据。 用户完全可以关闭 JavaScript,也可以修改请求内容,更可以绕过网页直接调用 PHP API。 因此一个成熟的 Web 应用应该允许前端验证和后端验证拥有部分重复逻辑。 这种重复并不是浪费,而是不同安全边界上的职责分工。 数据规范化应该和验证分开考虑 某些字段需要在存储前进行规范化。 例如日期可以统一成: YYYY-MM-DD 邮箱地址可以按照业务规则进行统一处理,用户名可以去除不必要的首尾空白,枚举字段可以限制为服务器允许的固定值。 但是规范化不能变成“看到奇怪字符就删除”。 如果一个字段要求保留原始文本,就应该保留其语义。 特别是姓名、地址、国际电话号码、多语言文本等数据,很容易因为过度过滤而破坏合法内容。 对于 Unicode 数据,也应该统一使用 UTF-8,并确保数据库连接、数据库字段、PHP 输出以及 HTML 页面之间的字符集配置一致。 敏感数据不能靠 Base64 保护 原稿中提到使用 base64_encode() 处理二进制数据,这一点需要特别强调。 Base64 是编码,不是加密。 任何人都可以很容易把 Base64 数据解码回来。因此绝不能把: 密码 API 密钥 身份认证令牌 信用卡等敏感信息 当成普通字符串进行 Base64 编码后,就认为它已经安全。 密码应该使用适合密码存储的单向哈希,例如 PHP 的 password_hash()。 如果业务确实需要以后恢复原文,则需要使用正规的加密方案和密钥管理机制,而不是 Base64。 日志也不能什么都记 验证日志对于排查问题和安全审计很有价值,但“所有验证失败的数据全部记录下来”同样可能造成新的安全问题。 日志里不应该随意保存密码、Session Token、完整身份认证凭据以及其他不必要的敏感信息。 更合理的日志通常记录: 请求时间 用户或会话标识 接口名称 验证失败类型 请求结果 IP及其他必要的审计信息 以及用于关联请求的 trace ID 或 request ID。 这样既能帮助排查异常,也不会因为日志本身变成新的敏感数据泄漏源。 最后一道防线应该是整个数据链,而不是一个验证函数 一个成熟的 PHP 表单处理流程通常应该包含多个层次。 首先确认请求方法和认证状态,然后读取输入数据,对字段进行类型、长度、格式和业务规则验证;必要时进行规范化;涉及权限的数据重新从服务器端读取;数据库操作使用 PDO 预处理语句;密码使用 password_hash();文件上传按照独立的安全规则处理;输出 HTML 时再根据上下文使用 htmlspecialchars();数据库本身通过字段类型、NOT NULL、UNIQUE、外键等约束保证数据完整性。 这些机制各自解决不同问题。 filter_var() 解决部分标准格式验证。 preg_match() 可以处理明确的自定义格式。 htmlspecialchars() 主要解决 HTML 输出编码。 PDO 预处理语句解决 SQL 参数和 SQL 代码之间的边界问题。 password_hash() 解决密码存储问题。 CSRF 防护解决部分状态改变请求的伪造问题。 数据库约束解决数据完整性问题。 HTTPS保护传输过程。 没有哪个 PHP 函数能够单独承担全部 Web 安全责任。 因此,PHP 表单开发最应该建立的习惯并不是“把用户输入过滤得干干净净”,而是从一开始就把客户端输入视为不可信数据,并根据它最终进入什么系统、什么语境,分别进行验证、规范化、参数化和输出编码。 浏览器可以帮助用户填写正确的数据,但浏览器没有资格决定服务器应该接受什么数据。服务器才是最终的信任边界;数据库则应该继续承担数据完整性的最后一道防线。

喜欢这篇报道?

使用下面的功能,方便以后继续阅读和分享 MNewsTV

设为 Google 新闻首选来源 让 Google 新闻优先显示 MNewsTV 的最新报道 ›
★ 我的收藏 查看已经收藏的文章
关于文章收藏 收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。 删除收藏请进入「我的收藏」进行管理。
分享这篇报道

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP