从0到1学PHP(七):PHP 与 HTML 表单:实现数据交互
目录
- 一、表单的创建与提交方式
- 1.1 HTML 表单的基本结构
- 1.2 GET 和 POST 提交方式的区别及适用场景
- 二、表单数据的接收与处理
- 2.1 使用\$_GET、\$_POST 超全局变量获取表单数据
- 2.2 对接收的数据进行验证
- 三、表单安全处理
- 3.1 防止 XSS 攻击的方法
- 3.2 防止 CSRF 攻击的措施
一、表单的创建与提交方式
1.1 HTML 表单的基本结构
在 HTML 中,<form>标签用于定义表单,它是一个容器,包含了各种表单元素,用于收集用户输入的数据。<form>标签有一些重要的属性,其中action属性指定了表单数据提交的目标 URL 地址,method属性则指定了提交表单数据时使用的 HTTP 方法,常见的有GET和POST。
例如:
<form action="submit.php" method="post"><!-- 表单元素将在这里添加 -->
</form>
在上述代码中,表单数据将被提交到submit.php页面,并且使用POST方法进行提交。
<input>元素是表单中最常用的元素之一,它可以根据type属性的不同,呈现出多种不同的输入类型,以满足各种数据收集需求。
- 当type="text"时,它是一个单行文本输入框,用于收集用户的文本输入,比如用户名、昵称等。例如:<input type=“text” name=“username” placeholder=“请输入用户名”>,其中name属性用于标识这个输入框,在后端接收数据时会用到,placeholder属性则提供了一个提示信息,告诉用户应该输入什么内容。
- type="password"用于创建密码输入框,用户输入的内容会被掩码显示,以保护密码安全。如:<input type=“password” name=“password” placeholder=“请输入密码”> 。
- type="radio"表示单选按钮,用于让用户从多个选项中选择一个。同一组单选按钮需要有相同的name属性值,通过value属性来区分不同的选项。例如:
<input type="radio" name="gender" value="male" id="male">
<label for="male">男</label>
<input type="radio" name="gender" value="female" id="female">
<label for="female">女</label>
这里通过<label>标签与<input>元素关联,当用户点击<label>标签时,对应的单选按钮也会被选中,增强了用户交互体验。for属性的值与<input>元素的id属性值相同,用于建立这种关联。
- type="checkbox"创建复选框,允许用户选择多个选项。 例如:
<input type="checkbox" name="hobby" value="reading" id="reading">
<label for="reading">阅读</label>
<input type="checkbox" name="hobby" value="sports" id="sports">
<label for="sports">运动</label>
同样,同一组复选框具有相同的name属性,每个复选框通过不同的value值来标识自己,<label>标签用于关联和增强交互。
除了<input>元素,表单中还有其他一些常用的标签。<textarea>标签用于创建多行文本输入区域,适合收集用户较长的文本内容,如留言、评论等。通过rows和cols属性可以设置文本区域的行数和列数,也可以通过 CSS 的width和height属性来控制其大小。例如:
<textarea name="message" rows="4" cols="50" placeholder="请输入您的留言"></textarea>
<select>标签用于创建下拉选择框,<option>标签则定义了下拉框中的选项。可以通过selected属性设置某个选项默认被选中。例如:
<select name="city"><option value="beijing">北京</option><option value="shanghai">上海</option><option value="guangzhou" selected>广州</option>
</select>
<button>标签用于创建按钮,当type="submit"时,它和<input type=“submit”>一样,用于提交表单数据;当type="button"时,它是一个普通按钮,通常需要配合 JavaScript 来实现特定的功能 ,比如点击弹出提示框等。例如:
<button type="submit">提交</button>
<button type="button" onclick="alert('这是一个普通按钮')">点击我</button>
1.2 GET 和 POST 提交方式的区别及适用场景
GET 和 POST 是 HTTP 协议中两种常用的提交表单数据的方式,它们在多个方面存在区别,了解这些区别有助于我们在实际开发中根据不同的业务需求选择合适的提交方式。
从数据传输位置来看,GET 方式会将表单数据附加在 URL 的查询字符串中,以?分隔 URL 和参数,参数之间用&连接。例如,当我们在浏览器地址栏中输入https://example.com/search?q=php,这里的q=php就是 GET 方式传递的参数。而 POST 方式则将数据放在 HTTP 请求体中进行传输,不会显示在 URL 中。
数据量限制方面,GET 方式由于受 URL 长度限制(不同浏览器对 URL 长度限制不同,一般在 2048 个字符左右),所以能传输的数据量较小,一般不适合传输大量数据。POST 方式理论上对数据量没有限制(实际上服务器可能会有一些配置限制,但通常比 GET 方式能传输的数据量大得多),适合传输大量的数据,比如文件上传、长文本内容提交等场景。
安全性上,GET 方式因为数据暴露在 URL 中,容易被缓存、记录在浏览器历史记录和服务器日志中,所以安全性较低,不适合传输敏感信息,如用户密码、银行卡号等。POST 方式数据在请求体中,相对不那么容易被获取到,安全性较高,但如果使用 HTTP 协议传输,数据仍然是明文传输的,在传输敏感信息时,建议结合 HTTPS 协议来提高安全性。
从幂等性角度来说,GET 请求是幂等的,多次发送相同的 GET 请求,对服务器资源的影响应该是相同的,即不会产生额外的副作用,只是获取相同的资源。例如多次请求https://example.com/articles/1获取文章内容,每次得到的结果应该是一样的。而 POST 请求通常不是幂等的,多次提交相同的 POST 请求可能会导致不同的结果,比如多次提交订单创建请求,可能会创建多个订单。
在适用场景上,GET 方式常用于数据查询操作,比如搜索功能、获取文章列表等。因为它将参数显示在 URL 中,方便用户分享链接,并且可以被搜索引擎爬虫抓取到。例如在电商网站中,用户搜索商品时,使用 GET 方式提交搜索关键词,方便用户将搜索结果分享给他人,且搜索引擎也能更好地索引这些内容。POST 方式则常用于数据的创建、修改和删除操作,以及提交敏感信息,如用户注册、登录时提交用户名和密码,向数据库中插入新的记录等。比如在用户注册页面,使用 POST 方式提交用户输入的注册信息,包括用户名、密码、邮箱等敏感信息,避免这些信息暴露在 URL 中。
二、表单数据的接收与处理
2.1 使用$_GET、$_POST 超全局变量获取表单数据
在 PHP 中,$_GET和$_POST是两个非常重要的超全局变量,用于获取 HTML 表单提交的数据。它们的使用方式相对简单直观,但在实际应用中,需要根据表单的提交方式来正确选择使用哪个变量。
当表单的method属性设置为GET时,我们使用$_GET变量来获取表单数据。例如,有如下一个简单的 HTML 表单:
<!DOCTYPE html>
<html><body><form action="process.php" method="get">用户名:<input type="text" name="username"><input type="submit" value="提交"></form></body></html>
在process.php文件中,可以使用以下 PHP 代码来获取提交的用户名:
<?php
if (isset($_GET['username'])) {$username = $_GET['username'];echo "欢迎你,$username";
}
?>
在上述代码中,首先通过isset($_GET[‘username’])来检查$_GET数组中是否存在名为username的键,这一步很重要,它可以避免在键不存在时产生未定义索引的错误。如果存在,就将其值赋给$username变量,然后进行后续的处理,这里只是简单地输出欢迎信息。
当表单的method属性设置为POST时,使用$_POST变量获取数据。比如下面这个表单用于收集用户的登录信息:
<!DOCTYPE html>
<html><body><form action="login.php" method="post">用户名:<input type="text" name="username">密码:<input type="password" name="password"><input type="submit" value="登录"></form></body></html>
在login.php文件中,获取表单数据的 PHP 代码如下:
<?php
if (isset($_POST['username']) && isset($_POST['password'])) {$username = $_POST['username'];$password = $_POST['password'];// 这里可以添加验证用户名和密码的逻辑,比如查询数据库等if ($username === 'admin' && $password === '123456') {echo "登录成功,欢迎 $username";} else {echo "用户名或密码错误";}
}
?>
同样,先检查$_POST数组中username和password键是否存在,存在则获取对应的值,然后进行登录验证逻辑。
对于一些复杂的数据,比如复选框、下拉框等,获取方式也类似。以复选框为例,假设有一个表单用于收集用户的兴趣爱好:
<!DOCTYPE html>
<html><body><form action="hobbies.php" method="post"><input type="checkbox" name="hobbies[]" value="reading">阅读<input type="checkbox" name="hobbies[]" value="sports">运动<input type="checkbox" name="hobbies[]" value="music">音乐<input type="submit" value="提交"></form></body></html>
在hobbies.php中获取数据:
<?php
if (isset($_POST['hobbies'])) {$hobbies = $_POST['hobbies'];echo "你的兴趣爱好有:";foreach ($hobbies as $hobby) {echo $hobby . " ";}
}
?>
由于复选框可以选择多个值,所以name属性设置为hobbies[],这样在 PHP 中通过$_POST[‘hobbies’]获取到的是一个数组,包含了用户选择的所有兴趣爱好值,通过遍历数组可以将其一一输出展示。
2.2 对接收的数据进行验证
对表单接收的数据进行验证是确保数据质量和安全性的重要步骤。下面介绍几种常见的验证方式及其在 PHP 中的实现。
非空验证是最基本的验证方式之一,用于确保用户没有留空必填字段。在 PHP 中,可以使用empty()函数来进行非空验证。例如,对于上面登录表单中的用户名和密码字段:
<?php
if (empty($_POST['username'])) {echo "用户名不能为空";exit;
}
if (empty($_POST['password'])) {echo "密码不能为空";exit;
}
// 后续处理代码
?>
这里通过empty($_POST[‘username’])检查username字段是否为空,如果为空则输出错误信息并终止脚本执行,避免后续处理中因空值导致的错误。
格式验证用于确保用户输入的数据符合特定的格式要求,比如邮箱地址、手机号码等。对于邮箱格式验证,通常使用正则表达式。PHP 提供了preg_match()函数来执行正则表达式匹配。邮箱的正则表达式模式一般为/^[a-zA-Z0-9._%±]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/ ,以下是验证邮箱的代码示例:
<?php
$email = $_POST['email']?? ''; // 使用空合并运算符确保变量存在,避免未定义索引错误
if (!preg_match('/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', $email)) {echo "邮箱格式不正确";exit;
}
// 后续处理代码
?>
上述代码中,preg_match()函数第一个参数是正则表达式模式,第二个参数是要验证的邮箱地址$email。如果匹配失败(即返回值为 0),则说明邮箱格式不正确,输出错误信息并终止脚本。
手机号码的格式验证也类似,中国手机号码的正则表达式模式可以是/^1[3-9]\d{9}$/,验证代码如下:
<?php
$phone = $_POST['phone']?? '';
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {echo "手机号码格式不正确";exit;
}
// 后续处理代码
?>
除了使用正则表达式,PHP 还提供了一些内置函数来辅助验证。例如,filter_var()函数可以用于验证和过滤各种类型的数据,包括邮箱、URL 等。使用filter_var()验证邮箱的代码如下:
<?php
$email = $_POST['email']?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {echo "邮箱格式不正确";exit;
}
// 后续处理代码
?>
这里filter_var()函数第一个参数是要验证的数据,第二个参数FILTER_VALIDATE_EMAIL表示验证邮箱格式,如果验证失败则返回false 。通过这些验证方式,可以有效地提高表单数据的准确性和可靠性,为后续的数据处理和存储提供保障。
三、表单安全处理
3.1 防止 XSS 攻击的方法
XSS(Cross-Site Scripting)攻击,即跨站脚本攻击,是一种常见的 Web 安全漏洞。攻击者通过在网页中注入恶意的 HTML 代码(如 JavaScript、CSS、HTML 标签等),当用户浏览该页面时,这些恶意代码会被执行,从而达到攻击用户的目的,比如窃取用户的 cookie、破坏页面结构、重定向到其他恶意网站等。
XSS 攻击主要分为三种类型:
- 反射型 XSS:这种攻击通常是一次性的,攻击者将恶意脚本通过 URL 参数传递给服务器,服务器将脚本作为响应的一部分返回,然后在用户的浏览器上执行。例如,当用户点击一个包含恶意脚本的链接时,服务器将恶意脚本反射回用户的浏览器并立即执行。攻击者会构造类似这样的恶意链接:https://example.com/search?q=%3Cscript%3Ealert(‘XSS’)%3C/script%3E,其中%3C和%3E分别是<和>的 URL 编码,当用户点击这个链接,服务器返回包含恶意脚本的搜索结果页面,浏览器解析页面时就会执行alert(‘XSS’)这段脚本。
- 存储型 XSS:恶意脚本被永久存储在服务器上(如数据库、消息论坛、访客留言等),当用户访问相应的网页时,存储的脚本会被执行。比如攻击者在论坛发布含有恶意脚本的帖子,其他用户查看该帖子时恶意脚本被执行。攻击者可以在论坛的留言输入框中输入恶意脚本<script>document.cookie=‘username=hacked;path=/’;</script>,提交后,这段脚本被存储到服务器数据库,当其他用户浏览该留言时,脚本就会在他们的浏览器中执行,将用户的username cookie 值修改为hacked。
- 基于 DOM 的 XSS:攻击发生在用户的浏览器端,通常是通过修改页面的 DOM 环境实现的,不需要服务器端的解析和响应。例如,攻击者利用网页中某个可以修改 DOM 元素的 JavaScript 函数漏洞,通过构造特殊的 URL 参数,使页面的 DOM 结构被修改并执行恶意脚本。假设网页中有一个根据 URL 参数显示不同内容的 JavaScript 代码:
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>DOM - based XSS</title>
</head><body><div id="content"></div><script>var urlParams = new URLSearchParams(window.location.search);var param = urlParams.get('param');document.getElementById('content').innerHTML = param;</script>
</body></html>
攻击者构造链接https://example.com/page.html?param=%3Cscript%3Ealert(‘XSS’)%3C/script%3E,用户点击该链接后,param参数的值被直接插入到div元素的innerHTML中,导致恶意脚本执行。
在 PHP 中,防止 XSS 攻击可以采取以下措施:
- 使用 htmlspecialchars () 函数:这是一种非常常用的方法,htmlspecialchars()函数会将一些特殊字符(比如<、>、&、"、')转换为相应的 HTML 实体,从而避免这些字符被浏览器解释为 HTML 代码。例如:
<?php
$user_input = '<script>alert("恶意脚本")</script>';
$safe_output = htmlspecialchars($user_input);
echo $safe_output;
?>
上述代码中,$user_input中的<script>标签会被转换为<script>,这样在页面上显示时就不会被当作 JavaScript 代码执行,而是直接显示文本内容<script>alert(“恶意脚本”)</script> 。
- 设置 HTTP 头:可以通过设置X-XSS-Protection头来启用浏览器内置的 XSS 过滤机制。在 PHP 中,可以使用header()函数来设置这个头,例如:
<?php
header('X-XSS-Protection: 1; mode=block');
?>
这表示启用 XSS 过滤,如果检测到 XSS 攻击,浏览器将阻止渲染页面。还可以设置Content-Security-Policy(CSP)头,它用于指定哪些资源(如脚本、样式表、图片等)可以被加载和执行,从而限制了恶意脚本的执行。比如:
<?php
header("Content-Security-Policy: default-src'self'; script-src'self'");
?>
上述设置表示只允许从当前站点(self)加载所有资源,并且只允许从当前站点加载脚本,这样可以有效防止外部恶意脚本的注入。
- 对用户输入进行严格验证和过滤:在接收用户输入时,使用正则表达式等方法对输入进行验证,确保输入内容符合预期格式,不包含恶意代码。例如,对于一个接收用户名的输入框,只允许输入字母、数字和下划线,可以这样验证:
<?php
$username = $_POST['username']?? '';
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {echo "用户名格式不正确,只能包含字母、数字和下划线";exit;
}
// 后续处理代码
?>
通过这种方式,将不符合要求的输入拦截下来,避免恶意代码进入系统 。
3.2 防止 CSRF 攻击的措施
CSRF(Cross-Site Request Forgery)攻击,即跨站请求伪造攻击,是一种常见的 Web 安全漏洞。攻击者通过诱导用户在已登录的网站上执行恶意操作,从而实现非法获取用户信息或执行操作的目的。
CSRF 攻击的原理是利用用户已经登录网站的信任状态。当用户登录到一个网站后,网站会在用户的浏览器中设置一个会话 cookie,用于标识用户的身份。只要用户的浏览器中存在这个有效的会话 cookie,网站就会认为该用户已经通过认证。攻击者通过构造一个恶意的链接或表单,当用户在已登录目标网站的情况下访问这个恶意链接或提交这个恶意表单时,用户的浏览器会自动带上目标网站的会话 cookie,向目标网站发送请求。由于目标网站无法区分这个请求是来自用户的真实操作还是攻击者的恶意伪造,就会根据请求内容执行相应的操作,从而导致 CSRF 攻击成功。
例如,假设一个银行网站允许用户通过 GET 请求进行转账操作,转账的 URL 格式为https://bank.example.com/transfer?amount=100&recipient=attacker,其中amount表示转账金额,recipient表示收款人。攻击者构造一个包含恶意链接的页面,如:
<!DOCTYPE html>
<html><body><img src="https://bank.example.com/transfer?amount=100&recipient=attacker" alt="看似无害的图片">
</body></html>
当用户在登录银行网站后,访问这个包含恶意链接的页面,浏览器会自动向银行网站发送转账请求,将 100 元转账给攻击者,而用户可能根本没有意识到发生了转账操作。
在 PHP 中,防止 CSRF 攻击可以采取以下措施:
- 使用 CSRF 令牌(Token):这是一种非常有效的防范方法。服务器在用户访问包含敏感操作(如修改密码、转账等)的页面时,生成一个唯一的、具有足够随机性的 CSRF 令牌。这个令牌可以通过安全的随机数生成算法来产生,例如在 PHP 中,可以使用random_bytes()函数生成一个随机字节序列,然后将其转换为十六进制字符串作为 CSRF 令牌。服务器将生成的令牌存储在用户的会话(Session)中。在 HTML 表单中,将 CSRF 令牌嵌入到一个隐藏的表单字段中。例如:
<form action="transfer.php" method="post"><input type="hidden" name="csrf_token" value="<?php echo bin2hex(random_bytes(32));?>"><input type="text" name="amount" value=""><input type="submit" value="转账">
</form>
当服务器接收到表单提交请求时,从请求中提取 CSRF 令牌(从表单字段中),然后与存储在用户会话中的令牌进行比较。如果两者一致,说明请求是合法的,来自用户正常操作的页面;如果不一致或者没有令牌,则拒绝该请求,返回错误信息。在 PHP 中验证 CSRF 令牌的代码示例如下:
<?php
session_start();
if (!isset($_POST['csrf_token']) ||!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {echo "CSRF验证失败,请求可能存在风险";exit;
}
// 处理转账逻辑
?>
这里使用hash_equals()函数来比较令牌,它可以防止时间攻击,确保令牌验证的安全性。
- 验证 Referer 字段:Referer 头信息包含了请求的来源 URL。通过检查这个头信息,服务器可以大致判断请求是否来自于合法的页面。例如,如果一个转账请求的 Referer 是银行网站自身的某个页面,那么这个请求很可能是合法的;但如果 Referer 是一个外部的、可疑的网站,就可能是 CSRF 攻击。在 PHP 中,可以通过$_SERVER[‘HTTP_REFERER’]来获取 Referer 字段的值并进行验证 。例如:
<?php
$referer = $_SERVER['HTTP_REFERER']?? '';
$allowed_domain = 'https://bank.example.com';
if (!str_starts_with($referer, $allowed_domain)) {echo "请求来源可疑,可能是CSRF攻击";exit;
}
// 处理请求逻辑
?>
不过,需要注意的是,一些用户可能会出于隐私原因禁用 Referer 头信息的发送,所以不能完全依赖 Referer 检查来防范 CSRF 攻击。而且,攻击者也有可能伪造 Referer 头信息,虽然在现代浏览器的安全机制下,伪造有一定难度,但仍然是一个潜在风险。因此,在使用 Referer 检查时,应该结合其他防范措施。
- 限制请求方法:限制 Web 前端页面中表单的提交方法,如只允许 POST 请求,禁止 GET 请求。这样,即使攻击者诱导用户点击恶意链接,也无法通过 GET 请求执行恶意操作。因为 GET 请求的参数会暴露在 URL 中,容易被攻击者利用来构造恶意链接,而 POST 请求的参数在请求体中,相对更安全。在 HTML 表单中,可以将method属性设置为post:
<form action="sensitive_action.php" method="post"><!-- 表单内容 -->
</form>
在 PHP 中,也可以在处理请求的代码中检查请求方法,只处理 POST 请求:
<?php
if ($_SERVER['REQUEST_METHOD']!== 'POST') {echo "只允许POST请求";exit;
}
// 处理POST请求逻辑
?>
通过以上这些措施,可以有效地防范 CSRF 攻击,保护用户的信息安全和网站的正常运行。