news 2026/10/3 4:44:25

如何在 PHP 页面中通过按钮点击将变量安全传递到另一页面

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何在 PHP 页面中通过按钮点击将变量安全传递到另一页面

前言

页面 A 上有个按钮,点击后要把一个变量(订单号、用户 ID、筛选条件)带到页面 B。很多人的第一版代码是把值拼进 URL,B 页面直接用$_GET读出来就用。功能能跑,问题在后面才冒出来:用户在地址栏把id=1改成id=2,看到了别人的订单;有人把带删除参数的链接丢进群里,搜索爬虫顺着链接就把数据删了;日志里出现一条带着脚本标签的参数,页面开始弹窗。

根因只有一句话:HTTP 是无状态的,两次请求之间没有任何「记忆」,变量必须落在客户端或服务端某一侧。落在客户端的那部分,用户随时能改;落在服务端的那部分,又要考虑多进程、多台机器怎么共享。很多人记住了「用 POST 更安全」,但 POST 只是把参数从 URL 挪进了请求体,它本身不提供任何安全保证——用 curl 一样能随意构造。

这篇文章按「先决定数据放哪 → 接收端怎么校验 → 令牌与转义 → 完整可运行示例」的顺序讲一遍。文中示例最低需要 PHP 8.1(用到了never返回类型,它是 PHP 8.1 引入的),可以直接用 PHP 内置服务器跑起来看到输出。

一、四种传递方式,先想清楚把数据放在哪

方式数据在哪用户能否篡改适合传什么不要传什么
URL 查询串(GET)地址栏、Referer、访问日志、浏览器历史完全可以无副作用的筛选条件、分页页码口令、令牌、金额、被当作授权依据的 ID
表单 POST请求体可以(伪造一个请求即可)会改变服务端状态的提交动作同样不能当授权依据
Session服务端存储,客户端只留会话 ID不能直接改,但可能被会话固定或劫持登录态、CSRF 令牌、跨页面中间数据海量数据(会撑爆会话存储)
Cookie客户端可以(除非做了签名校验)偏好设置、带签名的标识任何没签名的「可信」数据

表格里最关键的一列是「用户能否篡改」。结论很朴素:凡是经过客户端走一圈的东西,都要当成不可信输入(untrusted input)重新校验。这不是「加固」,而是正确性的一部分——你永远不知道中间那台代理、那个浏览器插件、那个手快的用户做了什么。

二、按钮点击的三种形态


  1. 表单提交:按钮是表单的一部分,点下去就是把表单里的字段以 POST(或 GET)发往 action 指向的地址。这是唯一「点击即提交」的形态。

  2. 链接跳转:按钮其实是个超链接,点下去就是一次 GET。用 GET 做删除是经典错误——浏览器预取、爬虫遍历、用户按刷新,都会重复触发。

  3. 前端异步请求:用 fetch 之类的接口发到后端,拿到结果再跳转。此时参数在请求体里,语义上和表单 POST 一样。


不管是哪种形态,接收端都要做同样三件事,而且顺序不能颠倒:

1. 确认请求方法正确(该 POST 的接口就不要接受 GET) 2. 校验 CSRF 令牌(针对会改变服务端状态的操作) 3. 校验并转换参数(白名单、类型、长度),任一失败立刻拒绝

第 2 步失败,说明这个请求根本不是从你自己的页面发出来的,直接拒绝即可,别抱着「先处理后校验」的侥幸。

三、接收端的三道防线

1. 类型校验:把「一串字符」变成「我认识的值」

安全做法是「只接受我认识的值」,而不是「过滤掉我讨厌的字符」。黑名单永远列不全,白名单天然安全。

$id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT); // 注意这是三态返回值:合法整数 / false(格式不对)/ null(字段压根不存在) if (!is_int($id) || $id < 1) { // 拒绝 }

filter_input()返回三态是高频踩坑点:很多人只判断=== false,字段完全缺失时拿到的是null,于是一路绿灯走进业务逻辑。

2. 输出转义:离开 PHP 那一刻才转义

数据存进数据库时不要转义(否则你会得到一堆&amp;),在输出到另一个上下文时才转义。输出到 HTML 用htmlspecialchars($v, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8');输出到 SQL 用预处理绑定;输出到 shell 用escapeshellarg()。

补充一个版本事实:PHP 8.1 起,htmlspecialchars()的默认flags 从ENT_COMPAT变成了ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401。但这不代表你可以省略参数——显式写出来,同一份代码在 PHP 8.0 上也一样安全。

3. CSRF 令牌:证明请求来自你自己的页面

$_SESSION['csrf_token'] ??= bin2hex(random_bytes(32));

比较时用hash_equals()而不是==,避免逐字节比较泄露时序信息。登录成功后要session_regenerate_id(true),防止会话固定(session fixation)。改变状态的操作一律用 POST 加令牌,处理完再用 303 跳转,也就是常说的 Post/Redirect/Get 模式。

四、完整可运行示例

把下面这段保存成handle.php,在它所在目录启动内置服务器即可。

<?php declare(strict_types=1); /** * handle.php —— 按钮点击后提交的目标页面 * 最低 PHP 8.1(never 返回类型是 PHP 8.1 引入的) * * 启动:php -S 127.0.0.1:8000 -t . */ session_start(); if (!isset($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } if (!isset($_SESSION['items'])) { $_SESSION['items'] = []; } /** 输出到 HTML 上下文时才必须转义;这里输出纯文本,仍然走一遍,方便你换成页面时直接复用 */ function esc(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8'); } function fail(int $status, string $message): never { http_response_code($status); header('Content-Type: text/plain; charset=utf-8'); echo $message, PHP_EOL; exit; } header('Content-Type: text/plain; charset=utf-8'); // ---------- 分支一:按钮点击后的提交 ---------- if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') { // 第一道:CSRF。用 hash_equals 做恒定时间比较 $token = (string)($_POST['csrf_token'] ?? ''); if ($token === '' || !hash_equals($_SESSION['csrf_token'], $token)) { fail(419, 'CSRF 校验失败:请求不是从本站页面发起的'); } // 第二道:类型校验。filter_input 是三态返回,别只判断 false $id = filter_input(INPUT_POST, 'id', FILTER_VALIDATE_INT); if (!is_int($id) || $id < 1) { fail(400, 'id 必须是大于 0 的整数'); } // 第三道:长度与存在性 $title = trim((string)($_POST['title'] ?? '')); if ($title === '' || mb_strlen($title, 'UTF-8') > 60) { fail(400, 'title 长度必须在 1~60 之间'); } // 业务写入(这里用 session 代替数据库) $_SESSION['items'][$id] = $title; // Post/Redirect/Get:303 让浏览器改用 GET 请求新地址,用户刷新不会重复提交 header('Location: /handle.php?done=' . $id, true, 303); exit; } // ---------- 分支二:GET,展示状态并下发令牌 ---------- $done = filter_input(INPUT_GET, 'done', FILTER_VALIDATE_INT); echo 'csrf_token=', $_SESSION['csrf_token'], PHP_EOL; if (is_int($done)) { echo '提交成功,id=', $done, PHP_EOL; } foreach ($_SESSION['items'] as $itemId => $itemTitle) { echo esc((string)$itemId), ' => ', esc($itemTitle), PHP_EOL; }

跑起来验证一遍:

# 1. 起服务(另开一个终端) php -S 127.0.0.1:8000 -t . # 2. 取令牌,同时用 cookie jar 保存会话 TOKEN=$(curl -s -c /tmp/jar.txt http://127.0.0.1:8000/handle.php | sed -n 's/^csrf_token=//p') # 3. 带令牌提交;-L 才会跟着 303 跳转 curl -s -b /tmp/jar.txt -L -X POST http://127.0.0.1:8000/handle.php \ -d "csrf_token=$TOKEN" -d "id=7" -d "title=hello" # 4. 不带令牌提交,看拒绝效果 curl -s -o /dev/null -w 'HTTP %{http_code}\n' -b /tmp/jar.txt \ -X POST http://127.0.0.1:8000/handle.php -d "id=7" -d "title=hello"
提交成功,id=7 7 => hello HTTP 419

常见坑点


  1. ❌ 直接用echo $_GET['name'];输出用户参数。


✅ 输出前统一走esc()转义;写入数据库时保持原样,不要提前转义。


  1. ❌ 用if ($id == 0)之类的松散比较判断参数合法性。


✅ 用filter_input()显式校验类型,注意它的三态返回值(合法值 /false/null)。


  1. ❌ 把「删除」做成一个超链接,参数放在 URL 上。


✅ 删除一律 POST + CSRF 令牌 + 服务端权限校验;GET 只做无副作用的读取。


  1. ❌ 只在前端校验,认为「按钮是我控制的,用户改不了」。


✅ 前端校验只是体验优化,服务端必须独立完整地再校验一遍。


  1. ❌if ($token == $_SESSION['csrf_token'])直接比较字符串。


✅ 用hash_equals($_SESSION['csrf_token'], $token)。


  1. ❌ 把数组或对象serialize()后塞进隐藏字段,接收端unserialize()。


✅ 只传标识(如 ID),数据从服务端或 Session 重新取;反序列化不可信输入会造成对象注入。


  1. ❌ 令牌签发后永不轮换,一个页面用一年。


✅ 登录成功后session_regenerate_id(true),令牌随会话生命周期更新。


  1. ❌ 用header('Location: ' . $_GET['back'])做「返回上一页」。


✅ 只允许跳转到白名单内的站内路径,否则就是一个开放重定向(open redirect)漏洞。

总结

场景推荐方式必须配套的动作
无副作用的筛选、分页GET 查询串类型校验、输出转义
会改变状态的操作表单 POSTCSRF 令牌、权限校验、Post/Redirect/Get
多页面共享的中间数据Session登录后重建会话 ID,敏感数据不落 Cookie
跨请求的一次性提示信息Session 中的 flash 数据取用后立即删除


安全传递变量的关键不是「用 POST」,而是承认客户端传来的一切都不可信,然后在服务端按顺序完成「CSRF 令牌 → 类型校验 → 权限校验 → 转义输出」这四步。把这四步固化成项目里一个统一的入口函数,比每次写页面时凭记忆加判断要可靠得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 4:44:11

OpenShell实战:统一终端配置,解决多端同步与命令复用

从开始用命令行那天起&#xff0c;我就一直在跟"换机器"这件事较劲。本地辛苦攒下来的别名、函数、补全规则&#xff0c;全散落在.zshrc、.bashrc、PowerShell 的$PROFILE里&#xff0c;换台电脑就得手动重来一遍&#xff0c;还总漏掉几个冷门配置。后来我把目光投向…

作者头像 李华
网站建设 2026/10/3 4:42:32

QwenPaw 从零上手:安装配置、参数调优与避坑指南

1. 从零上手 QwenPaw&#xff1a;这个工具到底解决什么问题第一次听到 QwenPaw 这个名字&#xff0c;很多人会下意识把它和某个模型权重、某个推理框架或者某个命令行工具联系起来。我最初接触它的时候也是这个反应&#xff0c;翻了一圈资料才理清楚&#xff1a;QwenPaw 本质上…

作者头像 李华
网站建设 2026/10/3 4:41:26

Windows + WSL2 部署 vLLM:大模型本地推理的完整实践指南

1. vLLM本地部署的整体设计与思路拆解先说结论&#xff1a;如果你在Windows上跑大模型推理&#xff0c;又不甘心只做UI聊天玩具&#xff0c;想正儿八经把vLLM跑起来、接API、走Docker分发&#xff0c;那么“Windows WSL2 vLLM HuggingFace/ModelScope”这条链路是目前最兼顾…

作者头像 李华
网站建设 2026/10/3 4:41:26

Python实现红外与可见光图像融合:从传统方法到深度学习实战

简介&#xff1a;这份资源面向图像处理初学者与计算机视觉方向的学习者&#xff0c;聚焦红外与可见光图像融合这一经典课题&#xff0c;帮助读者理解如何借助Python与小波变换将两类传感器图像的优势结合起来。红外图像反映温度分布&#xff0c;可见光图像保留形状、颜色与纹理…

作者头像 李华
网站建设 2026/10/3 4:41:23

Python零基础入门:1小时编写批量重命名小工具

从零到第一个能用的 Python 小工具&#xff0c;很多人被“编程很难”的刻板印象劝退了。实际上&#xff0c;Python 最友好的地方在于&#xff1a;你不必先啃完一本语法书&#xff0c;只要掌握最核心的几个概念&#xff0c;就能写出解决日常重复劳动的小程序。这篇文章就按“1 小…

作者头像 李华
网站建设 2026/10/3 4:40:33

嵌入式启动流程全景解析:从ROM Code到main函数

上周有个朋友发消息说板子“变砖”了&#xff0c;程序死活不跑&#xff0c;点复位也没反应&#xff0c;串口还打出一堆乱码。我让他拍了两张照片&#xff0c;结果发现BOOT0引脚悬空&#xff0c;芯片每次上电都进系统存储器自带的Bootloader&#xff0c;压根没执行Flash里的应用…

作者头像 李华