从零开始创建 Web 应用程序 - 第 8 部分(共 8 部分):安全性和未来

Avatar of Chris Coyier
Chris Coyier 发布

DigitalOcean 为您的旅程的每个阶段提供云产品。 立即开始使用 $200 免费积分!

👋 您好! 我们想提醒您,本系列文章附带的源代码不再提供下载。 我们仍然认为该系列提供了宝贵的知识,但鉴于我们已经走过了 10 多年的时间,我们也认为使用现代 PHP 框架(如 Laravel)甚至 JavaScript 框架(如 ReactVue)来创建渐进式 Web 应用程序是值得考虑的。

我们终于完成了! 首先,感谢您一直关注这个旅程。

在下面,我们将通过讨论我们做出的某些选择、安全预防措施以及我们(和您)对该应用程序 2.0 版本的想法来总结一些内容。

文章系列

  1. 规划应用程序:基本理念和设计
  2. 规划应用程序:数据库架构和开发方法
  3. 设计应用程序:工作流程图和 Photoshop 设计
  4. 设计应用程序:HTML 和 CSS
  5. 开发应用程序:用户交互
  6. 开发应用程序:添加 AJAX 交互
  7. 开发应用程序:列表交互
  8. 安全性和未来

面向对象编程

因为我们应该始终在编程时力求高效,所以我们在构建该应用程序时牢记 DRY 编程理念。 DRY 代表“不要重复自己”,应该成为我们编程理念的核心。

在我们看来,采用面向对象编程 (OOP) 方法是保持该应用程序 DRY 的最佳方式。 OOP 允许我们将常见的方法分组并分离任务,而无需在函数之间传递参数。 要详细了解 OOP 及其优势,请阅读 Jason 对 OOP 的介绍。

安全

安全在任何应用程序中都至关重要。 我们拥有存储数据的用户帐户。 这些用户信任我们来确保其数据的安全,包括其密码和他们输入到列表中的所有信息。 该应用程序已经非常安全。 密码以加密格式存储,永远不会通过电子邮件以明文形式发送。 与数据库进行的所有交互都是安全的。 只有登录的用户才能发出导致数据库更改的命令,并且这些用户只能发出影响其自身数据的命令。

但是,由于该应用程序中存在各种 AJAX 内容,因此我们的安全措施需要考虑更多情况。 首先,我们的 JavaScript(像所有 JavaScript 一样)是公开可见的。 此 JavaScript 包含用于进行 AJAX 调用的代码,这意味着我们将发送到的 URL 以及该 URL 预期的哪些数据。 这会向潜在攻击者提供有关他们如何发送恶意请求的大量信息。 因此,我们需要注意,确保所有传入数据都经过适当的转义。

服务器端的安全

避免服务器端的攻击涉及两个主要的风险因素:一是数据库攻击的可能性;二是恶意用户可能会提交危险数据,当从数据库中读取并显示出来时,会损害我们的应用程序或用户。 幸运的是,PHP 为我们提供了多种方法来应对这些风险。

PDO

数据库攻击,称为 SQL 注入,是一种特别恶劣的攻击形式。 恶意用户可以读取、操作或完全删除易受攻击的数据库。 这意味着我们必须非常重视防止任何类型的 SQL 注入发生。

幸运的是,PHP 数据对象 (PDO) 通过使用准备好的语句,几乎消除了 SQL 注入的风险,准备好的语句就像查询模板,我们可以使用参数对其进行自定义。 当将参数插入查询时,所有转义操作都会自动完成,因此在使用准备好的语句时,几乎不可能发生 SQL 注入。

正是由于这种强大的安全优势,我们为该应用程序选择了 PDO。(请记住,准备好的语句并不局限于 PDO;其他数据库扩展,例如 MySQLi,也支持它们。)

数据转义

虽然 PDO 可以有效地防止 SQL 注入,但它在我们将信息从数据库中读取出来时无济于事。 如果恶意用户将危险的标签注入我们的数据库,那么除非我们采取进一步措施来清理用户数据,否则它们在检索时仍然会很危险。

幸运的是,PHP 提供了内置函数,允许我们执行对用户输入的基本清理。 我们主要使用 strip_tags() 和白名单,以确保没有<script>标签或其他潜在的危险标签进入数据库。 此外,由于我们绝不允许此类标签,因此我们在将数据插入数据库之前执行此转义操作。

JavaScript 中的安全

首先,一个好方法是“打包”javascript,使其不易阅读,下载速度也更快。 许多工具可以做到这一点,包括 Dean Edwards 的这个工具

客户端清理

其次,由于我们输入数据并将其立即显示在屏幕上,因此最好直接在 JavaScript 中执行一些输入清理操作。 当用户输入新列表项时,我们将执行两个步骤来清理它。 首先,我们将确保他们不会恶意地尝试将立即执行的 JavaScript 插入链接。

// Check for JS in the href attribute
function cleanHREF(str) {
    return str.replace(/\<a(.*?)href=['"](javascript:)(.+?)<\/a>/gi, "Naughty!");
}

然后,我们还将清理该输入文本中的任何其他 HTML。 我们将允许一些 HTML,以防用户想要对列表进行格式化,例如使用<strong>标签等。 使用以下函数,我们将清除除白名单中设置的标签之外的所有标签。

注意: 下面使用的 strip_tags() 函数是 php.js 项目的一部分,该项目已将许多有用的 PHP 函数移植到 JavaScript。

var $whitelist = '<b><i><strong><em><a>',

// Strip HTML tags with a whitelist
function strip_tags(str, allowed_tags) {
 
    var key = '', allowed = false;
    var matches = [];
    var allowed_array = [];
    var allowed_tag = '';
    var i = 0;
    var k = '';
    var html = '';
 
    var replacer = function(search, replace, str) {
        return str.split(search).join(replace);
    };
 
    // Build allowes tags associative array
    if (allowed_tags) {
        allowed_array = allowed_tags.match(/([a-zA-Z]+)/gi);
    }
 
    str += '';
 
    // Match tags
    matches = str.match(/(<\/?[\S][^>]*>)/gi);
 
    // Go through all HTML tags
    for (key in matches) {
        if (isNaN(key)) {
            // IE7 Hack
            continue;
        }
 
        // Save HTML tag
        html = matches[key].toString();
 
        // Is tag not in allowed list? Remove from str!
        allowed = false;
 
        // Go through all allowed tags
        for (k in allowed_array) {
            // Init
            allowed_tag = allowed_array[k];
            i = -1;
 
            if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+'>');}
            if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+' ');}
            if (i != 0) { i = html.toLowerCase().indexOf('</'+allowed_tag)   ;}
 
            // Determine
            if (i == 0) {
                allowed = true;
                break;
            }
        }
 
        if (!allowed) {
            str = replacer(html, "", str); // Custom replace. No regexing
        }
    }
 
    return str;
}

这些函数在发送添加新列表项的 AJAX 请求之前在 js/lists.js 中实现……

. . .
    // AJAX style adding of list items
    $('#add-new').submit(function(){
        // HTML tag whitelist. All other tags are stripped.
        var $whitelist = '<b><i><strong><em><a>',
            forList = $("#current-list").val();
            newListItemText = strip_tags(cleanHREF($("#new-list-item-text").val()), $whitelist),
. . .

POST 与 GET

我们为应用程序安全采取的最后一个措施是,对所有 AJAX 调用使用 POST 而不是 GET。 这是因为 GET 方法应该仅用于检索,而不能用于以任何方式修改数据的操作。

不使用 GET 修改数据的主要原因是,使用 GET 发出的请求会包含在 URL 中(例如 http://example.com?get=request&is=this&part=here)。 根据 URL 中传递的信息修改数据存在固有风险,因为用户可能会因意外刷新其浏览器而导致重复处理。

使用 POST 的第二个,不那么重要的原因是,使用 POST 发送虚假请求要困难一些,这可以对恶意用户起到(轻微)的威慑作用。

2.0 功能

当然,我们作为设计师和开发人员的工作永远不会完成。这是一个简单易用的列表应用程序的良好开端,但立即就有新的功能浮现在脑海中。以下是一些扩展功能的想法。也许它们会稍微使事情变得复杂,但如果实施得当,它们都是很棒的想法。

  • 列表分享
    输入电子邮件地址以与他人分享列表。分享是指真正意义上的协作编辑。用户需要一个帐户,因此,如果他们已经拥有帐户,他们将收到电子邮件并被要求加入列表(他们可以选择接受或不接受)。如果该电子邮件地址没有帐户,他们将被提示先加入。
  • 多个列表
    现在用户只能拥有一个列表。对于用户来说,拥有多个列表可能很有用。也许可以使用下拉菜单在列表之间切换,并使用一个简单的按钮添加新列表。这里有很多界面需要考虑,包括确定如何删除列表。
  • RSS
    每个列表都可以拥有自己的 RSS Feed。可能需要一些选项,例如 RSS Feed 中将包含什么内容(例如,您是否希望看到列表项完成或未完成时的条目?)。Feed URL 可能是长篇大论的 URL,因此它们本质上是完全私密的,除非被明确分享。
  • iPhone 界面
    通过 iPhone 或其他移动设备登录将提供更好的优化体验。

系列作者

Jason Lengstorf 是一位居住在蒙大拿州米苏拉的软件开发人员。他是《PHP for Absolute Beginners》的作者,并定期撰写有关编程的博客文章。当他不坐在键盘前时,他很可能在排队买咖啡,酿造自己的啤酒,或梦想成为一名“流言终结者”成员。
Chris Coyier 是一位目前居住在伊利诺伊州芝加哥的设计师。他是《Digging Into WordPress》的合著者,同时也是设计领域的所有内容的博主和演讲者。远离电脑时,他很可能在电视上对着足球教练大喊大叫,或者弹着班卓琴。