滚动新闻 →
笔记本电脑屏幕一闪就黑,可能涉及背光、电源还是排线问题 不交社保影响孩子考学? 传广东多地强制交社保 新兴新冠变异株 SV.2.4横行中国南方 美打击朝鲜IT渗透网络 起诉14人含10中国人 黄奇帆落马“政坛不倒翁”为何倒下 传南博原院长徐湖平狱中亡 判刑细节未公开惹疑 摄像头遍布难保民安 专家揭中共监控真相 美国社安金明年涨3.6% 每月增75美元 内外交困 IMF下调中国经济增长预测 美拟收紧工作签证续签 条件不变也须重审 黄奇帆终落马 受薄熙来、孙政才案牵连? 惨!众多中国人翻垃圾桶找吃的 不分老幼 Windows 11 文件资源管理器搜索怎么用?文件很多时如何快速找到目标 PHP 表单验证有哪些原则?不要让浏览器提交的数据直接进入数据库 从《水调歌头》看苏轼的旷达 中共再惩戒沈伯洋父亲公司 遭呛还有啥招 美捣毁朝鲜IT渗透网络 起诉14人10人在中国 俄罗斯疫情引发恐慌 内蒙呼伦贝尔突删鼠疫警示 住豪宅 涉洗钱 捐政界 加国侨领原荣祥将被遣返 沙特反击胡塞武装 川普尚未明言是否出兵 川普否认停止供乌情报 泽连斯基提议美乌续谈 桃园市议会通过“反跨境镇压”提案 跨党护民主防线 苹果秀新品HomeView可辨识人脸 攻智慧家庭 卢思位入住民宿遭警盘查 谢阳被夺探视权 四通桥事件四周年 北京再现看桥员 五中全会前扩大维稳 沈明室:中共在怕什么 蒜与中国传统饮食文化 书法中的藏锋和露锋如何理解 如何培养孩子保护弱者的责任感 巴黎火车站大火浓烟滚滚 尚无人员伤亡报告 高分辨率视频为什么占用更多存储空间 中国27岁女网红35楼坠下奇迹生还 自曝痛苦历程 马斯克不爽!呛记忆体趁火打劫 影射三星、SK客户全都会记住! 隋朝为什么能够重新统一中国 卫生间潮湿问题应该如何处理 沙特反手炸油库!胡塞燃料基地被轰 西安市长失踪! 中国大闸蟹跌至“地板价”一只仅卖5、6元 香港发生珠宝劫案 40岁男抢劫珠宝店后逃跑 陆剧男群演动作不当惹怒全网 剧组急撤片调查 豆腐为什么能够成为中国重要的传统食品

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

发布时间: 2026-10-14 12:00:02    最后更新: 2026-10-14 13:21:40    阅读:14  约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