题 如何在PHP中阻止SQL注入?


如果在未修改SQL查询的情况下插入用户输入,则应用程序将变得容易受到攻击 SQL注入,如下例所示:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

这是因为用户可以输入类似的东西 value'); DROP TABLE table;--,查询变为:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

可以采取哪些措施来防止这种情况发生?


2781


起源




答案:


使用预准备语句和参数化查询。 这些是由数据库服务器与任何参数分开发送和解析的SQL语句。这样攻击者就无法注入恶意SQL。

你基本上有两个选项来实现这个目标:

  1. 运用 PDO (对于任何支持的数据库驱动程序)

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. 运用 库MySQLi (对于MySQL):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // do something with $row
    }
    

如果您要连接到MySQL以外的数据库,则可以参考特定于驱动程序的第二个选项(例如 pg_prepare() 和 pg_execute() 对于PostgreSQL)。 PDO是通用选项。

正确设置连接

请注意,使用时 PDO 访问MySQL数据库 真实 准备好的陈述是 默认情况下不使用。要解决此问题,您必须禁用预准备语句的模拟。使用PDO创建连接的示例是:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

在上面的例子中,错误模式并不是绝对必要的, 但建议添加它。这样脚本就不会停止了 Fatal Error 什么时候出错了。它为开发人员提供了机会 catch 任何错误 thrown as PDOException秒。

什么是 强制性然而,这是第一个 setAttribute() line,告诉PDO禁用模拟的预准备语句并使用 真实 准备好的陈述这样可以确保PHP在将语句和值发送到MySQL服务器之前不会对其进行解析(使攻击者可能无法注入恶意SQL)。

虽然你可以设置 charset 在构造函数的选项中,重要的是要注意PHP的“旧”版本(<5.3.6) 默默地忽略了charset参数 在DSN中。

说明

会发生什么是您传递给的SQL语句 prepare 由数据库服务器解析和编译。通过指定参数(a ? 或者像。这样的命名参数 :name 在上面的示例中,您告诉数据库引擎要筛选的位置。然后当你打电话 execute,准备好的语句与您指定的参数值组合在一起。

这里重要的是参数值与编译语句结合,而不是SQL字符串。 SQL注入通过在创建SQL以发送到数据库时欺骗脚本来包含恶意字符串。因此,通过将参数中的实际SQL分开发送,可以限制结束您不想要的内容的风险。使用预准备语句时发送的任何参数都将被视为字符串(尽管数据库引擎可能会进行一些优化,因此参数最终也可能作为数字)。在上面的例子中,如果 $name变量包含 'Sarah'; DELETE FROM employees 结果只是搜索字符串 "'Sarah'; DELETE FROM employees",你不会最终得到 一张空桌子

使用预准备语句的另一个好处是,如果在同一个会话中多次执行相同的语句,它只会被解析和编译一次,从而为您带来一些速度提升。

哦,既然你问过如何为插入操作,这是一个例子(使用PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));

准备好的语句可以用于动态查询吗?

虽然您仍然可以为查询参数使用预准备语句,但动态查询本身的结构无法进行参数化,并且某些查询功能无法进行参数化。

对于这些特定方案,最好的办法是使用限制可能值的白名单过滤器。

// Value whitelist
// $dir can only be 'DESC' otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

7954



只是为了补充,因为我没有在其他任何地方看到它,另一道防线是 Web应用防火墙 (WAF)可以设置规则来查找sql​​注入攻击: - jkerak
另外,mysql_query的官方文档只允许执行一个查询,所以除了之外的任何其他查询;被忽略了。即使已经弃用,PHP 5.5.0下也有很多系统可以使用这个功能。 php.net/manual/en/function.mysql-query.php - Randall Valenciano
这是一个坏习惯,但是是一个问题后的解决方案:不仅用于SQL注入,而且用于任何类型的注入(例如,在F3框架v2中有一个视图模板注入孔)如果你有一个现成的旧网站或应用程序正在遭受从注入缺陷来看,一种解决方案是在引导程序中使用转义值重新分配超级全局预定义变量(如$ _POST)的值。通过PDO,仍然可以转义(也适用于今天的框架):substr($ pdo-> quote($ str,\ PDO :: PARAM_STR),1,-1) - Alix
这个答案缺乏对准备好的陈述的解释 - 一件事 - 如果你在请求期间使用了大量准备好的陈述,那么它会受到性能影响,有时它会导致10倍的性能损失。更好的情况是使用PDO参数绑定关闭,但语句准备关闭。 - donis
使用PDO更好,如果您使用直接查询,请确保使用mysqli :: escape_string - Kassem Itani


警告:   这个答案的示例代码(如问题的示例代码)使用PHP mysql 扩展,在PHP 5.5.0中已弃用,并在PHP 7.0.0中完全删除。

如果您使用的是最新版本的PHP,那么 mysql_real_escape_string 下面列出的选项将不再可用(但是 mysqli::escape_string 是一个现代的等价物)。这几天了 mysql_real_escape_string 选项只适用于旧版PHP上的遗留代码。


你有两个选择 - 逃避你的特殊角色 unsafe_variable,或使用参数化查询。两者都可以保护您免受SQL注入。参数化查询被认为是更好的做法,但在使用之前需要在PHP中更改为更新的mysql扩展。

我们将首先覆盖较低影响的字符串。

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

另见,细节 mysql_real_escape_string 功能。

要使用参数化查询,您需要使用 库MySQLi 而不是 MySQL的 功能。要重写您的示例,我们需要类似以下内容。

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

你想要阅读的关键功能就是 mysqli::prepare

此外,正如其他人所建议的那样,您可能会发现使用类似的东西来增加抽象层是有用的/更容易的 PDO

请注意,您询问的案例非常简单,更复杂的案例可能需要更复杂的方法。尤其是:

  • 如果要根据用户输入更改SQL的结构,参数化查询将无济于事,并且不需要转义所需的转义 mysql_real_escape_string。在这种情况下,您最好通过白名单传递用户的输入,以确保只允许“安全”值。
  • 如果你在一个条件中使用来自用户输入的整数并采取 mysql_real_escape_string 方法,你会遇到所描述的问题 多项式 在下面的评论中。这种情况比较棘手,因为整数不会被引号括起来,所以你可以通过验证用户输入只包含数字来处理。
  • 可能还有其他我不知道的情况。你可能会发现 这个 对于您可能遇到的一些更微妙的问题,它是一个有用的资源。

1514



运用 mysql_real_escape_string 是足够还是我必须使用参数化? - peiman F.
@peimanF。即使在本地项目上,也要保持使用参数化查询的良好实践。使用参数化查询 保证 不会有SQL注入。但请记住,您应该清理数据以避免虚假检索(即XSS注入,例如将HTML代码放入文本中) htmlentities 例如 - Goufalite
@peimanF。参数化查询和绑定值的良好实践,但真正的转义字符串现在是好的 - Richard


这里的每个答案仅涵盖部分问题。
事实上,有  我们可以动态添加的不同查询部分:

  • 一个字符串
  • 一个号码
  • 标识符
  • 语法关键字。

准备好的报表只涵盖其中的两个

但有时我们必须使查询更加动态,同时添加运算符或标识符。
因此,我们需要不同的保护技术。

通常,这种保护方法是基于 白名单。 在这种情况下,每个动态参数都应该在脚本中进行硬编码,并从该集合中进行选择。
例如,要进行动态排序:

$orders  = array("name","price","qty"); //field names
$key     = array_search($_GET['sort'],$orders)); // see if we have such a name
$orderby = $orders[$key]; //if not, first one will be set automatically. smart enuf :)
$query   = "SELECT * FROM `table` ORDER BY $orderby"; //value is safe

但是,还有另一种方法来保护标识符 - 转义。只要你有一个引用的标识符,你可以通过加倍来逃避内部的反引号。

作为进一步的步骤,我们可以从准备好的语句中借用一些占位符(代表查询中的实际值的代理),并发明另一种类型的占位符 - 标识符占位符。

因此,长话短说:这是一个 占位符不是 准备好的声明 可以被视为银弹。

因此,一般性建议可以表述为
只要您使用占位符向查询添加动态部分(当然这些占位符已正确处理),您可以确保查询是安全的

但是,SQL语法关键字存在问题(例如 ANDDESC 等等,但白名单似乎是这种情况下唯一的方法。

更新

虽然对SQL注入保护的最佳实践有一个普遍的一致意见,但也有 还有很多不好的做法。 其中一些根深蒂固的PHP用户心中。例如,在这个页面上有(虽然对大多数访问者来说是不可见的) 超过80个删除的答案  - 由于质量差或促进不良和过时的做法,所有被社区删除。更糟糕的是,一些不好的答案不是删除而是繁荣。

例如, 有(1)  是(2)  仍然(3)  许多(4)  答案(5), 包含 第二个最受欢迎的答案 建议你手动字符串转义 - 一种被证明是不安全的过时方法。

或者有一个稍微好一点的答案 另一种字符串格式化方法 甚至称它为终极灵丹妙药。当然,事实并非如此。这种方法并不比常规字符串格式更好,但它保留了所有缺点:它仅适用于字符串,并且与任何其他手动格式一样,它基本上是可选的,非强制性的度量,容易出现任何类型的人为错误。

我认为所有这一切都是因为一个非常古老的迷信,得到了诸如此类机构的支持 OWASP 要么 PHP手册,它宣称任何“逃避”和防止SQL注入之间的平等。

无论PHP手册多年来所说的是什么, *_escape_string 决不会使数据安全 从来没有打算过。除了对字符串以外的任何SQL部分无用之外,手动转义是错误的,因为它是手动的,与自动化相反。

OWASP使情况变得更糟,强调逃避 用户输入 这完全是胡说八道:在注射保护的背景下应该没有这样的词。每个变量都有潜在危险 - 无论来源如何!或者,换句话说 - 每个变量都必须正确格式化以便放入查询中 - 无论来源是什么。这是重要的目的地。在开发人员开始将绵羊与山羊分开的那一刻(想想某个特定变量是否“安全”),他迈出了迈向灾难的第一步。更不用说即使是措辞也表明在入口点大量逃避,类似于非常神奇的引用功能 - 已经被鄙视,弃用和删除。

所以,不像任何“逃避”,准备好的陈述  确实可以防止SQL注入的措施(适用时)。

如果你仍然不相信,这是我写的一步一步的解释, Hitchhiker的SQL注入预防指南在那里我详细解释了所有这些问题,甚至编写了一个完全致力于不良做法及其披露的部分。


946



伟大的,经过深思熟虑的文章。我可能会补充说,使用PHP的Sanitize过滤器是一种(但不完全是)一种白色列表。例如, FILTER_SANITIZE_NUMBER_INT 只允许数字字符,从而白名单字符,而不是整个字符串。结合准备好的陈述,它是一个很好的“腰带和吊带”的方法。 - Sablefoste
@Sablefoste你不需要在这里列入白名单。任何消毒都是多余的。遵循的规则越少,你犯的错误就越少。虽然您可以进行任何验证,但是为了应用程序逻辑而不是数据库。 - Your Common Sense


我建议使用 PDO (PHP数据对象)运行参数化SQL查询。

这不仅可以防止SQL注入,还可以加快查询速度。

而使用PDO而不是 mysql_mysqli_,和 pgsql_ 函数,您可以使您的应用程序从数据库中抽象出来,在极少数情况下您必须切换数据库提供程序。


770



是不是PDO为MySQL DB包装了mysqli?在这种情况下,它肯定不会比mysqli更快。我仍然会推荐它。这是一个比mysqli API更好的界面。 - Peter Bagnall
使用参数化查询可以加快查询速度。从技术上讲,mysqli的速度可能会更快。服务器响应查询所花费的实际时间超过了因使用包装器而可能发生的时间差异。但是mysqli与数据库绑定在一起。如果要使用其他数据库引擎,则必须更改使用mysqli的所有调用。 PDO不是这样。 - Kibbee
PDO不支持动态 order by 不幸的是:( - Horse


使用 PDO 和准备好的查询。

$conn 是一个 PDO 目的)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

567



从 维基百科:准备语句对SQL注入具有弹性,因为稍后使用不同协议传输的参数值无需正确转义。如果原始语句模板不是从外部输入派生的,则不能进行SQL注入。 - Imran


如您所见,人们建议您最多使用预准备语句。这没错,但是当你的查询被执行时 就一次 每个过程,会有轻微的性能损失。

我正面临着这个问题,但我想我已经解决了这个问题 非常 复杂的方式 - 黑客用来避免使用引号的方式。我将它与模拟的预处理语句结合使用。我用它来防止 所有 各种可能的SQL注入攻击。

我的方法:

  • 如果您希望输入为整数,请确保它是  整数。在PHP这样的变量类型语言中就是这样 非常 重要。您可以使用这个非常简单但功能强大的解决方案: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input); 

  • 如果你期望整数的其他东西 六角形。如果你将它变成十六进制,你将完全逃脱所有输入。在C / C ++中有一个叫做的函数 mysql_hex_string(),在PHP中你可以使用 bin2hex()

    不要担心转义字符串的原始长度是原始长度的2倍,因为即使你使用它也是如此 mysql_real_escape_string,PHP必须分配相同的容量 ((2*input_length)+1),这是一样的。

  • 传输二进制数据时经常使用这种十六进制方法,但我认为没有理由不在所有数据上使用它来防止SQL注入攻击。请注意,您必须使用前置数据 0x 或使用MySQL功能 UNHEX 代替。

所以,例如,查询:

SELECT password FROM users WHERE name = 'root'

会变成:

SELECT password FROM users WHERE name = 0x726f6f74

要么

SELECT password FROM users WHERE name = UNHEX('726f6f74')

Hex是完美的逃脱。无法注射。

UNHEX函数和0x前缀之间的区别

评论中有一些讨论,所以我最后要说清楚。这两种方法非常相似,但它们在某些方面略有不同:

** 0x **前缀只能用于数据列,例如 char,varchar,text,block,binary等
此外,如果您要插入空字符串,它的使用有点复杂。你必须完全替换它 '',否则你会收到错误。

UNHEX() 继续努力 任何 柱;你不必担心空字符串。


Hex方法通常用作攻击

请注意,此十六进制方法通常用作SQL注入攻击,其中整数就像字符串一样,只是转义为 mysql_real_escape_string。然后你可以避免使用引号。

例如,如果您只是这样做:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

攻击可以给你注入 容易。考虑从脚本返回的以下注入代码:

SELECT ... WHERE id = -1 union all来自information_schema.tables的select table_name

现在只需提取表结构:

SELECT ... WHERE id = -1 union all table_schema.column中select table_name,其中table_name = 0x61727469636c65

然后只需选择想要的任何数据。不是很酷吗?

但是如果可注射部位的编码器会使其十六进制,则无法进行注射,因为查询看起来像这样: SELECT ... WHERE id = UNHEX('2d312075...3635')


501



@SumitGupta是的,你做到了。 MySQL没有与之连接 + 但随着 CONCAT。性能:我不认为它会影响性能,因为mysql必须解析数据,如果origin是string或hex则无关紧要 - Zaffy
@YourCommonSense您遇到什么错误?请明确点。 - Zaffy
@YourCommonSense你不明白这个概念......如果你想在mysql中使用字符串,你可以这样引用它 'root' 或者你可以诅咒它 0x726f6f74 但如果你想要一个数字并将其作为字符串发送,你可能会写'42'而不是CHAR(42)...'''''''''''''' 0x3432不 0x42 - Zaffy
@YourCommonSense我无话可说......只是大声笑......如果您仍想在数字字段上尝试十六进制,请参阅第二条评论。我跟你打赌,它会起作用。 - Zaffy
@YourCommonSense你还不明白吗?你不能使用0x和concat,因为如果字符串是空的,你将以错误结束。如果您想要简单的替代查询,请试试这个 SELECT title FROM article WHERE id = UNHEX(' . bin2hex($_GET["id"]) . ') - Zaffy


重要

防止SQL注入的最佳方法是使用 准备好的陈述  而不是逃避作为 接受的答案 演示。

有像这样的图书馆 Aura.Sql 和 EasyDB 允许开发人员更容易地使用预准备语句。要了解更多有关准备好的陈述更好的原因 停止SQL注入,参考 这个 mysql_real_escape_string() 旁路 和 最近修复了WordPress中的Unicode SQL Injection漏洞

注射预防 - mysql_real_escape_string()

PHP有一个特殊的功能来防止这些攻击。你需要做的只是使用一口功能, mysql_real_escape_string

mysql_real_escape_string 获取将在MySQL查询中使用的字符串,并返回相同的字符串,并安全地转义所有SQL注入尝试。基本上,它将取代用户可能使用MySQL安全替代品输入的那些麻烦的引号('),一个转义引用\'。

注意: 您必须连接到数据库才能使用此功能!

//连接MySQL

$name_bad = "' OR 1'"; 

$name_bad = mysql_real_escape_string($name_bad);

$query_bad = "SELECT * FROM customers WHERE username = '$name_bad'";
echo "Escaped Bad Injection: <br />" . $query_bad . "<br />";


$name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; 

$name_evil = mysql_real_escape_string($name_evil);

$query_evil = "SELECT * FROM customers WHERE username = '$name_evil'";
echo "Escaped Evil Injection: <br />" . $query_evil;

您可以在中找到更多详细信息 MySQL - SQL注入预防


455



这是使用传统mysql扩展可以做的最好的事情。对于新代码,建议您切换到mysqli或PDO。 - Álvaro González
我不同意这个“防止这些攻击的特殊功能”。我觉得 mysql_real_escape_string 目的是允许为每个输入数据字符串构建正确的SQL查询。预防sql-injection是这个功能的副作用。 - sectus
你不使用函数来写正确的输入数据字符串。你只需编写不需要转义或已经转义的正确的。 mysql_real_escape_string()可能是为了你提到的目的而设计的,但它唯一的价值就是防止注入。 - Nazca
警告!  mysql_real_escape_string()  并非绝对可靠。 - eggyal
mysql_real_escape_string 现已弃用,因此它不再是可行的选择。它将来会从PHP中删除。最好转向PHP或MySQL人推荐的内容。 - jww


安全警告:这个答案不符合安全最佳实践。 转义不足以阻止SQL注入, 使用 准备好的陈述 代替。使用下面列出的策略需要您自担风险。 (也, mysql_real_escape_string() 在PHP 7中删除了。)

你可以做一些基本的事情:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

这不会解决所有问题,但它是一个非常好的垫脚石。我省略了明显的项目,例如检查变量的存在,格式(数字,字母等)。


413



我试过你的例子,这对我来说很好。你能清楚“这不会解决所有问题” - Chinook
如果你不引用字符串,它仍然是可注射的。采取 $q = "SELECT col FROM tbl WHERE x = $safe_var"; 例如。设置 $safe_var 至 1 UNION SELECT password FROM users 在这种情况下,因为缺少报价而起作用。也可以使用将字符串注入查询 CONCAT 和 CHR。 - Polynomial
@Polynomial完全正确,但我认为这仅仅是错误的用法。只要你正确使用它,它肯定会起作用。 - glglgl
警告!  mysql_real_escape_string()  并非绝对可靠。 - eggyal
mysql_real_escape_string 现已弃用,因此它不再是可行的选择。它将来会从PHP中删除。最好转向PHP或MySQL人推荐的内容。 - jww


无论您最终使用什么,请确保检查您的输入是否已被破坏 magic_quotes 或者其他一些善意的垃圾,如果有必要的话,可以通过它 stripslashes 或者其他什么来消毒它。


347



确实;开启magic_quotes运行只会鼓励糟糕的练习。但是,有时您无法始终将环境控制到该级别 - 您无权管理服务器,或者您的应用程序必须与(颤抖)依赖此类配置的应用程序共存。出于这些原因,编写可移植应用程序是件好事 - 尽管如果你控制部署环境显然会浪费你的努力,例如:因为它是一个内部应用程序,或者只能在您的特定环境中使用。 - Rob
从PHP 5.4开始,被称为“魔术引号”的憎恶已经存在 死了。并且很好地摆脱了糟糕的垃圾。 - BryanH


参数化查询和输入验证是要走的路。尽管如此,在许多情况下可能会发生SQL注入 mysql_real_escape_string() 已经用过。

这些示例容易受到SQL注入:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

要么

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

在这两种情况下,您都无法使用 ' 保护封装。

资源意外的SQL注入(当转义不够时)


330



如果采用输入验证技术,可以防止SQL注入,在该技术中,用户输入根据一组定义的长度,类型和语法规则以及业务规则进行身份验证。 - Josip Ivic


在我看来,在PHP应用程序(或任何Web应用程序)中通常阻止SQL注入的最佳方法是考虑应用程序的体系结构。如果防止SQL注入的唯一方法是记住每次与数据库通信时都使用一个特殊的方法或函数来做正确的事情,那么你做错了。这样,你忘记在代码中的某个时刻忘记正确格式化查询只是时间问题。

采用MVC模式和类似的框架 CakePHP的 要么  可能是正确的方法:创建安全数据库查询等常见任务已经解决并在此类框架中集中实施。它们可以帮助您以合理的方式组织Web应用程序,并使您更多地考虑加载和保存对象,而不是安全地构建单个SQL查询。


281



我认为你的第一段很重要。理解是关键。此外,每个人都不是为公司工作。对于大量的人来说,框架实际上违背了这个想法 理解。在截止日期前工作时,与基本面的关系可能并不重要,但是那些自己动手的人喜欢弄脏自己。框架开发人员没有那么特权,其他人必须低头并假设他们永远不会犯错误。做出决策的权力仍然很重要。谁能说我的框架将来不会取代其他一些计划? - Anthony Rutledge
@AnthonyRutledge你是绝对正确的。这非常重要 理解 发生了什么,为什么。然而,真正经过尝试和积极使用和开发的框架遇到并解决了许多问题并修补了许多安全漏洞的可能性已经相当高。查看源代码以了解代码质量是个好主意。如果这是一个未经测试的混乱,它可能不安全。 - Johannes Fahrenkrug
这里。这里。好点。但是,您是否同意许多人可以学习并学习采用MVC系统,但不是每个人都可以手动复制(控制器和服务器)。人们可以在这一点上走得太远。在加热我女朋友给我制作的花生酱山核桃饼干之前,我是否需要了解我的微波炉? ;-) - Anthony Rutledge
@AnthonyRutledge我同意!我认为用例也有所不同:我是为个人主页建立照片库还是建立网上银行网络应用程序?在后一种情况下,了解安全性的细节以及我正在使用的框架如何解决这些问题非常重要。 - Johannes Fahrenkrug
啊,安全例外,自己做推论。看,我倾向于愿意冒这一切,然后破产。 :-)开玩笑。有足够的时间,人们可以学习制作一个非常安全的应用程序。太多人急于求成。他们举手并假设框架是 更安全。毕竟,他们没有足够的时间来测试和解决问题。此外,安全是一个需要专门学习的领域。由于理解算法和设计模式,它不仅仅是程序员深入了解的东西。 - Anthony Rutledge