vlambda博客
学习文章列表

读书笔记《developing-microservices-with-node-js》安全性和可追溯性

Chapter 5. Security and Traceability

安全是当今系统中最大的担忧之一。 大公司泄露的信息量令人担忧,尤其是因为 90% 的信息泄露可以通过软件开发人员非常小的行动来弥补。事件记录和错误可追溯性也会发生类似的情况。在有人请求您没有的日志以审核失败之前,没有人会真正关注太多。在本章中,我们将在以下主题的帮助下讨论如何管理安全性和日志记录,以使我们的系统安全且可追溯:

  • 基础架构逻辑安全:我们将在中讨论如何保护我们的软件基础架构为了在我们的通信中提供行业标准的安全层。

  • 应用安全:我们将介绍保护我们的应用的常用技术。输出编码或输入验证等实践是行业标准,它们可以使我们免于灾难。

  • 可追溯性:在微服务中必须能够跟踪我们系统周围的请求 架构。我们将把这项任务用到 Seneca,并学习如何从这个奇妙的框架中获取信息。

  • 审计:即使是虽然我们尽最大努力构建软件,意外发生。重建调用序列并准确查看发生了什么的能力很重要。我们将讨论如何启用我们的系统以便能够恢复所需的信息。

Infrastructure logical security


基础设施安全性通常被软件工程师忽略,因为它与他们的领域完全不同的专业知识。然而,如今,尤其是如果你的职业倾向于 DevOps,这是一个不容忽视的主题。

在本书中,我们不会深入探讨基础设施的安全性,而只是通过一些经验法则来确保您的微服务安全。

强烈建议读者阅读和了解密码学以及围绕使用 SSH 的所有含义,SSH 是 当今保持通信安全的主要资源。

SSH – encrypting the communications

在任何组织中,都有一份可以访问某些服务的人员的严格列表。通常,这些服务的身份验证是通过用户名和密码完成的,但也可以使用密钥来完成验证用户身份 .

无论使用何种身份验证方法,都应始终通过安全通道(例如 SSH)进行通信。

SSH 代表 Secure Shell,它是一个用于访问远程机器中的 shell,但它也可以是一个非常有用的工具来创建代理和隧道来访问远程服务器。

让我们使用以下命令解释它是如何工作的:

/home/david:(develop) x ssh [email protected]
The authenticity of host '192.168.0.1 (192.168.0.1)' can't be established.
RSA key fingerprint is SHA256:S22/A2/eqxSqkS4VfR1BrcDxNX1rmfM1JkZaGhrjMbk.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.0.1' (RSA) to the list of known hosts.
[email protected]'s password:
Last login: Mon Jan 25 02:30:21 2016 from 10.0.2.2
Welcome to your virtual machine.

在这种情况下,我使用 Vagrant 来促进虚拟机的构建。 Vagrant 是一个非常流行的工具,用于自动化开发环境及其网站(https://www.vagrantup.com/) 包含有用的信息。

在第一行,我们执行 ssh [email protected] 命令。此命令尝试在 192.168.0.1 主机中以用户 david 的身份打开终端。

由于这是第一次对 IP 192.168.0.1 中的机器执行此命令,因此我们的计算机将不信任远程服务器。

这是通过在 /home/david/.ssh/known_hosts 文件夹下维护一个名为 known_hosts 的文件来完成的(在这种情况下)(这将取决于用户)。

此文件是具有相应密钥的主机列表。如您所见,以下两行说明 主机不可信任 并显示远程服务器持有的密钥的指纹,以便验证它:

The authenticity of host '192.168.0.1 (192.168.0.1)' can't be established.
RSA key fingerprint is SHA256:S22/A2/eqxSqkS4VfR1BrcDxNX1rmfM1JkZaGhrjMbk.

此时,用户应该通过检查密钥来验证服务器的身份。完成后,我们可以指示 SSH 连接到服务器,这将导致打印以下日志:

Warning: Permanently added '192.168.0.1' (RSA) to the list of known hosts.

现在,如果我们检查我们的 known_hosts 文件,我们可以看到密钥已添加到列表中, 如下:

读书笔记《developing-microservices-with-node-js》安全性和可追溯性

known_hosts 文件中存储的这个密钥是远程服务器的公钥。

SSH 使用一种称为 密码算法 RSA。该算法围绕非对称加密的概念构建,如图所示在下图中:

读书笔记《developing-microservices-with-node-js》安全性和可追溯性

非对称密码学依赖于一组密钥:一个公共密钥和一个私有密钥。顾名思义,公钥可以与所有人共享;而私钥必须保密。

用私钥加密的消息只能用公钥解密,反之几乎是不可能的(除非有人拿到另一半密钥)来拦截和解密消息。

此时,我们的计算机知道服务器的公钥,我们可以开始与服务器的加密会话。一旦我们得到终端,所有命令和这些命令的结果都将被加密并通过网络发送。

此密钥也可用于连接到没有密码的远程服务器。我们唯一需要做的就是在我们的机器中生成一个 SSH 密钥,并将其安装在服务器中的 .ssh 下名为 authorized_keys 的文件中 文件夹,known_hosts 文件所在的位置。

在使用微服务时,您可以远程登录到相当多的不同机器上,这样这种方法就变得更有吸引力了。但是,我们需要非常小心处理私钥的方式,因为如果用户泄露该私钥,我们的基础设施可能会受到损害。

Application security


应用程序安全性变得越来越重要。随着云正在成为大公司基础设施的事实标准,我们不能依赖数据被限制在单个数据中心的事实。

通常,当有人开始一项新业务时,主要关注点是从功能的角度构建产品。安全性不是主要关注点,通常会被忽视。

这是一种非常危险的做法,我们将通过让读者了解可能危及我们的应用程序的主要安全威胁来对其进行修改。

以安全方式开发应用程序的主要四大安全点如下:

  • 注射

  • 跨站脚本

  • 跨站请求伪造令牌保护

  • 打开重定向

在本节结束时,我们将能够识别主要漏洞,但我们不会对恶意攻击者进行防御。一般来说,软件工程师应该与最新的安全技术保持同步。无论您构建的产品多么好,如果它不安全,就会有人发现并利用它。

Common threats – how to be up to date

正如我们之前所说,安全性是应用程序开发中的一个持续主题。无论您正在构建什么类型的软件,它总会存在安全隐患。

在我的职业生涯中,我发现在不成为全职安全工程师的情况下了解 Web 开发安全的最佳方法是遵循 OWASP 项目。 OWASP 代表 Open Web Application Security Project,他们产生了相当一个有趣的 文档(除其他外),每年称为 OWASP Top 10。

Note

OWASP Top 10 于 2003 年首次发布,其目标是提高开发社区对应用程序开发中最常见威胁的认识。

在上一节中,我们确定了软件开发人员可能面临的四个主要安全问题,所有这些问题都将在以下各节中提及。

Injection

Injection 是迄今为止我们可能面临的最危险的攻击。具体来说,SQL 注入是影响应用程序的最常见的注入形式,它包括攻击者在我们的一个应用程序查询中强制执行 SQL 代码,导致到可能损害我们公司数据的不同查询。

还有其他类型的注入,但我们将重点关注 SQL 注入,因为现代世界中几乎每个应用程序都使用关系数据库。

SQL 注入包括在我们的应用程序中通过来自未经验证的源(例如 Web 表单或具有任意文本输入的任何其他数据源)的输入在我们的应用程序中注入或操作 SQL 查询。

让我们考虑以下示例:

SELECT * FROM users WHERE username = 'username' AND password = 'password'

Tip

永远不要将密码明文存储在数据库中。始终对它们进行哈希和加盐以避免彩虹表攻击。这只是一个例子。

此查询将为我们提供与给定名称和密码对应的用户。为了从客户端的输入构建查询,我们可以考虑执行类似于以下代码的操作作为一个好主意:

var express = require('express');
var app = express();
var mysql      = require('mysql');

var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'me',
  password : 'secret',
  database : 'test_db'
});

app.get('/login', function(req, res) {
  var username = req.param("username");
  var password = req.param("password");
  
  connection.connect();
  var query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
  connection.query(query, function(err, rows, fields) {
    if (err) throw err;
    res.send(rows);
  });
  connection.end();
});

app.listen(3000, function() {
  console.log("Application running in port 3000.");
});

乍一看,它看起来像一个简单的程序,它访问名为 test_db 的数据库并发出查询以检查是否存在与用户名和密码匹配的用户并将其返回给client 这样如果我们打开浏览器并尝试浏览到 http://localhost:3000/login? username=david&password=mypassword URL,浏览器将使用以下查询的结果呈现一个 JSON 对象:

SELECT * FROM users WHERE username = 'david' AND password = 'mypassword'

还没有什么奇怪的,但是如果客户试图入侵我们会发生什么?

看看下面的输入:

http://localhost:3000/login?username=' OR 1=1 --&password=mypassword

可以看到,它生成的查询是如下代码:

SELECT * FROM users WHERE username = '' OR 1=1 -- AND password = 'mypassword'

在 SQL 中,-- 字符序列用于注释该行的其余部分,以便有效查询如下:

SELECT * FROM users WHERE username='' OR 1=1

此查询返回完整的用户列表,如果我们的软件正在使用此查询的结果来解决用户是否应该登录,那么我们遇到了一些严重的问题。我们刚刚向甚至不知道有效用户名的人授予了对我们系统的访问权限。

这是关于 SQL 注入如何影响我们的众多示例之一。

在这种情况下,很明显我们正在将不受信任的数据(来自用户)连接到我们的查询中,但是相信我,当软件变得更加复杂时,识别起来并不总是那么容易。

避免 SQL 注入的一种方法是使用准备好的语句。

Input validation

应用程序主要通过表单与用户交互。这些表单通常包含可能导致攻击的自由文本输入字段。

防止损坏的数据进入我们的服务器的最简单方法是通过输入验证,正如 名称所暗示的那样,它包括验证用户的输入以避免前面描述的情况。

有两种类型的输入验证,如下所示:

  • 白名单

  • 黑名单

黑名单是一种危险技术。在大多数情况下,尝试定义输入中不正确的内容比简单地定义我们期望的内容要付出更多的努力。

推荐的方法是(并且将永远是)白名单来自用户的数据,通过 使用正则表达式:我们知道电话号码的样子,我们也知道用户名的样子,等等。

输入验证并不总是那么容易。如果您曾经遇到过电子邮件验证,您就会知道我在说什么:验证电子邮件的正则表达式绝非简单。

验证某些数据没有简单的方法这一事实不应限制我们这样做,因为省略输入验证可能会导致严重的安全漏洞。

输入验证不是 SQL 注入的灵丹妙药,但它也有助于解决其他安全威胁,例如跨站点脚本。

在上一节的查询中,我们做了一件非常危险的事情:将用户输入连接到我们的查询中。

一种解决方案可能是使用某种转义库来清理用户的输入,如下所示:

app.get('/login', function(req, res) {
  var username = req.param("username");
  var password = req.param("password");
  
  connection.connect();
  var query = "SELECT * FROM users WHERE username = '" + connection.escape(username) + "' AND password = '" + connection.escape(password) + "'";
  connection.query(query, function(err, rows, fields) {
    if (err) throw err;
    res.send(rows);
  });
  connection.end();
});

在这种情况下,使用的 mysql 库提供了一套方法来转义字符串。让我们看看它是如何工作的:

var mysql = require('mysql');
var connection = mysql.createConnection({
  host: 'localhost',
  username: 'root',
  password: 'root'
});

console.log(connection.escape("' OR 1=1 --"))

前面的小脚本转义了前面示例中作为 username 提供的字符串,结果是 \' OR 1=1 --

如您所见, escape() 方法已经替换了危险字符,净化了来自用户。

Cross-site scripting

跨站脚本,也称为XSS,是一种主要影响Web应用程序的安全漏洞。这是最常见的安全问题之一,对客户的影响可能很大,因为有人可能会通过这种攻击窃取用户身份。

该攻击是一种注入第三方网站的代码,可以从客户端的浏览器中窃取数据。有几种方法可以做到这一点,但到目前为止,最常见的是来自客户端的非转义输入。

在 Internet 上的少数网站中,用户可以添加包含任意输入的评论。这种任意输入可以包含从远程服务器加载 JavaScript 的脚本标签,这些脚本可以窃取会话 cookie(或其他类型的有价值信息),让攻击者在远程计算机上复制用户会话。

XSS 攻击主要有两种 类型:persistent非持久性

persistent 类型的 XSS 包括通过 制作一个特定的文本字符串,一旦在网站上显示给用户,就会解析为攻击。此代码可以通过存储在数据库中的任意输入文本(例如论坛中的评论)注入。

non-persistent 类型的 XSS 是当攻击插入到由于糟糕的数据处理,应用程序的非持久部分。

让我们看一下下面的截图:

读书笔记《developing-microservices-with-node-js》安全性和可追溯性

如您所见,我们在 http://www 中搜索了一本书(本书) .amazon.co.uk/。它不会产生任何输出(因为该书尚未出版),但它指定 您的搜索“microservices nodejs”不匹配任何产品 ,它以某种方式使用来自网络浏览器的输入作为输出。更重要的是,当我点击搜索时,亚马逊将我重定向到以下 URL:

http://www.amazon.co.uk/s/ref=nb_sb_noss?url=search-alias%3Daps&field-keywords=microservices+nodejs

我们知道 Amazon 是安全的,但如果它对 XSS 攻击敏感,我们可以修改 field-keywords 参数的值来制作一个在内容,导致攻击者能够窃取会话 cookie,这可能会导致网站出现一些 严重问题。

Output encoding

防止这种攻击的一种方法是输出编码。我们之前做过,当我们在 Input validation 部分使用 connection.escape()本章的。公平地说,我们应该验证用户输入的所有数据并对来自第三方的所有输出进行编码。这包括用户输入的内容,以及来自系统外部的信息来源。

在将问题缩小到 Web 开发时,我们必须了解需要输出编码的三个不同领域:

  • CSS

  • JavaScript

  • HTML

最有问题的两个是 JavaScript 和 HTML,攻击者可以轻松窃取信息而无需太多努力。

通常,无论我们使用哪个框架来构建我们的应用程序,它总是具有对输出进行编码的功能。

Cross-site request forgery

跨站请求伪造 (CSRF) 与跨站点请求脚本。在跨站点请求 脚本中,问题在于客户端信任来自服务器的数据。对于跨站点请求伪造,问题在于服务器信任来自客户端的数据。

窃取会话cookie后,攻击者不仅可以窃取用户的信息,还可以修改与cookie关联的账户信息。

这是通过通过 HTTP 请求将数据发布到服务器来完成的。

HTTP 将其请求分类为方法。方法基本上是用来指定请求要携带的操作是什么。最有趣的四种方法如下:

  • GET:从服务器获取数据。它不应修改任何持久数据。

  • POST:这会在服务器中创建一个资源。

  • PUT:这会更新服务器中的资源。

  • DELETE:这会从服务器中删除一个资源。

还有更多方法(例如PATCHCONNECT),但让我们关注这四个。如您所见,这四种方法中的三种会修改来自服务器的数据,具有有效会话的用户可能会窃取数据、创建付款、订购商品等。

避免跨站请求伪造攻击的一种方法是保护 POSTPUTDELETE 带有跨站点请求令牌的端点。

看看下面的 HTML 表单:

<form action="/register" method="post">
  <input name="email" type="text" />
  <input name="password" type="password" />
</form>

此表格描述了一种完全有效的情况:用户在我们的网站上注册;非常简单,但仍然有效且有缺陷。

我们指定 一个 URL 和预期参数列表,以便攻击者可以在几分钟内注册数百或数千个帐户,使用一个小脚本发出带有两个参数(emailpassword)的 POST 请求身体。

现在,看下面的表格:

<form action="/register" method="post">
  <input name="email" type="text" />
  <input name="password" type="password" />
  <input name="csrftoken" type="hidden" value="as7d6fasd678f5a5sf5asf" />
</form>

您可以看到不同之处:有一个额外的隐藏参数,称为 csrftoken

该参数是每次呈现表单时生成的随机字符串,以便我们可以将此额外参数添加到每个表单。

提交表单后,将验证 csrftoken 参数以仅让具有有效令牌的请求通过并生成要再次在页面上呈现的新令牌。

Open redirects

有时,我们的 应用程序可能需要将用户重定向到某个 URL。例如,当在没有有效身份验证的情况下点击私有 URL 时,用户通常会被重定向到登录页面:

http://www.mysite.com/my-private-page

这可能会导致重定向到以下内容:

http://www.mysite.com/login?redirect=/my-private-page

这听起来是合法的。用户被发送到登录页面,一旦他提供了一组有效的凭据,它就会被重定向到 /my-private-page

如果有人试图窃取我们用户的帐户会发生什么?

查看以下请求:

http://www.mysite.com/login?redirect=http://myslte.com

这是一个精心设计的请求,会将用户重定向到 myslte.com 而不是 mysite.com(注意 l 而不是 i)。

有人可以让 myslte.com 看起来像 mysite.com 的登录页面,并通过分发前面的内容窃取您的用户密码和用户名社交媒体中的 URL,因为用户将被重定向到恶意页面。

上述问题的解决方案很简单:不要将用户重定向到不受信任的第三方网站。

同样,执行此类任务的最佳方法是将目标主机列入白名单以进行重定向。基本上,我们不会让我们的软件将我们的客户重定向到未知网站。

Effective code reviews

减少应用程序中的安全漏洞的最最有效的方法之一是通过系统化和知情的代码审查流程。代码审查的问题在于,它们最终总是成为意见和个人偏好的转储区域,这通常不仅不会提高代码的质量,而且还会导致最后一刻的更改,从而可能暴露我们应用程序中的漏洞。

产品开发生命周期中用于安全代码审查的专门阶段有助于大幅减少交付到生产环境的错误数量。

软件工程师面临的问题是,他们的思维被训练来构建运行良好的东西,但他们没有发现缺陷的心态,尤其是围绕他们构建的东西。这就是为什么你不应该测试你自己的代码(比开发时进行的测试更进一步),更不用说安全测试你的应用程序了。

但是,我们通常在团队中工作,这使我们能够审查其他人的代码,但我们必须以有效的方式进行。

代码审查需要与编写软件一样多的脑力,尤其是在您审查复杂代码时。你不应该花费超过两个小时来审查相同的功能,否则会错过重要的缺陷,并且对细节的关注会降低到令人担忧的程度。

这在基于微服务的架构中不是什么大问题,因为功能应该足够小,以便在合理的时间内阅读,尤其是当您与作者讨论他正在尝试构建的内容时。

您应该始终遵循两阶段审查,如下所示:

  • 快速查看代码以了解全局:它是如何工作的,它使用了哪些你不熟悉的技术,它是否做了它应该做的事情,等等

  • 按照要查找的项目清单查看代码

此项目列表必须预先确定,并取决于您公司正在构建的软件的性质。

通常,在代码审查期间检查代码安全问题的项目列表非常大,但我们可以将其缩小到以下组件:

  • 所有输入是否在适用时都经过验证/编码?

  • 是否所有输出都已编码,包括日志?

  • 我们是否使用跨站点请求伪造令牌保护 端点?

  • 所有用户凭证是否在数据库中加密或散列?

如果我们检查此列表,我们将能够确定我们应用程序中最大的安全问题。

Traceability


可追溯性在现代信息系统中极其重要。这是微服务中的一个微妙问题,在 Seneca 中优雅地解决了,使得请求很容易在我们的系统中跟踪,以便我们可以审计失败。

Logging

Seneca 在日志记录方面非常。在 Seneca 中可以配置很多选项,以便获取有关一切工作方式(如果工作正常)的所需信息。

让我们看看 日志记录如何与一个小型应用程序一起工作:

var seneca = require("seneca")();

seneca.add({cmd: "greeter"}, function(args, callback){
  callback(null, {message: "Hello " + args.name});
});

seneca.act({cmd: "greeter", name: "David"}, function(err, result) {
  console.log(result);
});

这是可以编写的最简单的 Seneca 应用程序。让我们按如下方式运行它:

seneca  node index.js
2016-02-01T09:55:40.962Z 3rhomq69cbe0/1454579740947/84217/- INFO hello Seneca/1.0.0/3rhomq69cbe0/1454579740947/84217/-
{ message: 'Hello David' }

这是使用默认日志记录配置运行应用程序的结果。除了我们在代码中使用的 console.log() 方法之外,还有一些关于 Seneca 被记录的内部信息。有时,您可能只想记录您的应用程序正在生成的内容,以便您可以调试应用程序而不会产生任何噪音。在这种情况下,只需运行以下命令:

seneca  node index.js --seneca.log.quiet
{ message: 'Hello David' }

但是,有时,系统中会出现奇怪的行为(甚至是使用的框架中的错误),而您想要获取有关正在发生的事情的所有信息. Seneca 也支持这一点,如以下命令所示:

seneca  node index.js --seneca.log.print

前面的命令 将打印无穷无尽的信息,这些信息可能没有帮助。

为了减少 Seneca 生成的日志记录量,对记录到输出的内容进行了细粒度控制。让我们看一下以下几行:

2016-02-01T10:00:07.191Z dyy9ixcavqu4/1454580006885/85010/- DEBUG register install transport {exports:[transport/utils]} seneca-8t1dup
2016-02-01T10:00:07.305Z dyy9ixcavqu4/1454580006885/85010/- DEBUG register init seneca-y9os9j
2016-02-01T10:00:07.305Z dyy9ixcavqu4/1454580006885/85010/- DEBUG plugin seneca-y9os9j DEFINE {}
2016-02-01T10:00:07.330Z dyy9ixcavqu4/1454580006885/85010/- DEBUG act root$       IN o5onzziv9i7a/b7dtf6v1u9sq cmd:greeter {cmd:greeter,name:David} ENTRY (mnb89) - - -

它们是来自前面代码示例的日志输出的随机行,但它会为我们提供有用的信息:这些条目是 Seneca 框架上不同操作(例如插件、注册和操作)的调试级日志行。为了过滤它们,Seneca 提供了对我们想要查看的级别或操作的控制。考虑以下示例:

node index.js --seneca.log=level:INFO

这只会输出与 INFO 级别相关的日志:

seneca  node index.js --seneca.log=level:INFO
2016-02-04T10:39:04.685Z q6wnh8qmm1l3/1454582344670/91823/- INFO hello Seneca/1.0.0/q6wnh8qmm1l3/1454582344670/91823/-
{ message: 'Hello David' }

还可以按动作类型过滤,挺有意思的。当您使用微服务时,了解流中发生的事件链是审核故障时需要查看的第一件事。通过 Seneca 为我们提供的对日志记录的控制,只需执行以下命令即可:

node index.js --seneca.log=type:act

这将产生 以下输出:

读书笔记《developing-microservices-with-node-js》安全性和可追溯性

如您所见,所有 前面的行都对应于 act 类型,甚至更多,如果我们从上到下跟随命令的输出,我们确切地知道 Seneca 反应的事件顺序及其顺序。

Tracing requests

跟踪请求也是一项非常重要的活动,有时,它甚至是法律要求,特别是如果您在金融领域工作。同样,Seneca 非常擅长跟踪请求。对于每个呼叫,Seneca 都会生成一个唯一标识符。这个标识符可以在调用的所有路径中被追踪到,如下所示:

var seneca = require("seneca")();

seneca.add({cmd: "greeter"}, function(args, callback){
  console.log(this.fixedargs['tx$']);
  callback(null, {message: "Hello " + args.name});
});
seneca.act({cmd: "greeter", name: "David"}, function(err, result) {
  console.log(this.fixedargs['tx$']);
});

在这里,我们将包含 Seneca 中的事务 ID 的字典记录到终端。因此,如果我们执行它,我们将得到以下输出:

2016-02-04T10:58:07.570Z zl0u7hj3hbeg/1454583487555/95159/- INFO hello Seneca/1.0.0/zl0u7hj3hbeg/1454583487555/95159/-
3jlroj2n91da
3jlroj2n91da

您可以看到 Seneca 中的所有请求是如何被跟踪的:框架分配一个 ID 并跨端点传播。在这种情况下,我们所有的端点都在本地机器上,但是如果我们将它们分布在不同的机器上,ID 仍然是相同的。

有了这个唯一的 ID,我们将能够在我们的系统中重建客户数据的旅程,并且 使用相关的时间戳对请求进行排序,我们 可以准确了解用户在做什么,每个动作花了多少时间,与延迟相关的可能问题是什么,等等。通常,结合断路器输出信息的日志记录允许工程师在非常短的时间内解决问题。

Auditing

到目前为止,我们一直在使用console.log()将数据输出到日志中。这是一个不好的做法。它破坏了日志的格式并将内容抛出到 标准输出。

再次,塞内卡来救援:

var seneca = require("seneca")();

seneca.add({cmd: "greeter"}, function(args, callback){
  this.log.warn(this.fixedargs['tx$']);
  callback(null, {message: "Hello " + args.name});
});

seneca.act({cmd: "greeter", name: "David"}, function(err, result) {
  this.log.warn(this.fixedargs['tx$']);
});

让我们看看 Seneca 产生了什么输出:

seneca  node index.js
2016-02-04T11:17:28.772Z wo10oa299tub/1454584648758/98550/- INFO hello Seneca/1.0.0/wo10oa299tub/1454584648758/98550/-
2016-02-04T11:17:29.156Z wo10oa299tub/1454584648758/98550/- WARN - - ACT 02jlpyiux70s/9ca086d19x7n cmd:greeter 9ca086d19x7n
2016-02-04T11:17:29.157Z wo10oa299tub/1454584648758/98550/- WARN - - ACT 02jlpyiux70s/9ca086d19x7n cmd:greeter 9ca086d19x7n

如您所见,我们现在使用记录器输出事务 ID。我们生成了 WARN 消息,而不是简单的控制台转储。从现在开始,我们可以使用 Seneca 日志过滤器来隐藏我们操作的输出,以便专注于我们正在尝试查找的内容。

Seneca 提供以下五个级别的日志记录:

  • DEBUG:用于在开发应用程序时调试应用程序并跟踪 生产系统中的问题。

  • INFO:此日志级别用于生成有关事件的重要消息,例如事务已经开始或完成。

  • WARN:这是警告级别。我们在系统发生不好的事情时使用它,但这并不重要,用户通常不会受到影响;但是,这表明某些事情正在以错误的方式进行。

  • ERROR:用于记录错误。通常,用户会受到它的影响,它也会中断流程。

  • 致命:这是最灾难性的级别。它仅在发生不可恢复的错误并且系统将无法正常运行时使用。

产生不同级别的日志的一种方法是使用相关函数。正如我们之前所见,我们调用this.log.warn() 来记录警告。如果我们调用 this.log.fatal() 方法,我们将记录一个致命错误,与其他级别相同。

Tip

尝试在开发过程中调整应用程序中的日志,否则当生产中发生不良情况时,您会后悔缺少信息。

通常,INFO、DEBUG 和 WARN 将是最常用的日志级别。

HTTP codes

HTTP 代码经常被忽略,但它们是标准化远程服务器响应的非常重要的机制。

程序(或用户)向服务器发出请求时,可能会发生一些事情,如下所示:

  • 可能会成功

  • 它可能无法通过验证

  • 它可能会产生服务器错误

如您所见,可能性是无穷无尽的。我们现在遇到的问题是 HTTP 是为机器之间的通信而创建的。我们如何处理机器将读取这些代码的事实?

HTTP 以一种非常优雅的方式解决了这个问题:每个请求都必须使用 HTTP 代码来解决,并且这些代码具有指示代码性质的范围。

1xx – informational

100-199 范围内的代码仅供参考。这个范围内最有趣的代码是 102 代码。此代码用于指定操作正在后台进行,并且可能需要一些时间才能完成。

2xx – success codes

成功代码 用于指示 HTTP 请求的一定程度的成功。它是最常见(也是最需要的)代码。

该范围内最常见的代码如下:

  • 200: Success:此代码表示完全成功。即使是远程也没有出错。

  • 201: Created:此代码主要用于客户端请求在服务器中创建新实体时的 REST API。

  • 203:非权威信息:此代码旨在用于通过转换代理路由请求时,源以 200 响应。

  • 204: No Content:这是一个成功的代码,但是服务器没有返回任何内容。有时,即使没有内容,API 也会返回 200。

  • 206: Partial Content:此代码用于分页响应。发送一个标头,指定客户端将接受的范围(和偏移量)。如果响应大于范围,服务器将回复 206,表示还有更多数据要跟踪。

3xx – redirection

300 到 399 范围内的代码表示客户端必须采取一些额外的操作才能完成请求。

该范围内最常见的代码描述如下:

  • 301:已永久移动:此状态码表示客户端试图获取的资源已永久移动到另一个位置。

  • 302: Found:此代码表明用户出于某种原因需要执行临时重定向,但浏览器开始将此代码实现为 303请参阅其他。这导致引入了 303 和 307 临时重定向 代码来消除行为重叠的歧义。

  • 308 Permanent Redirect:此代码,顾名思义,用于指定资源的永久重定向。可能会与 301 混淆,但有一点不同,308 代码不允许更改 HTTP 方法。

4xx – client errors

400 到 499 范围内的代码代表客户端产生的错误。它们表明请求存在问题。这个范围特别重要,因为它是 HTTP 服务器必须向客户端指示他们的请求有问题的方式。

该范围内的常用代码如下:

  • 400 Bad Request:此代码表示来自用户的请求在语法上不正确。可能缺少参数或某些值未通过验证。

  • 401 Unauthorized:此代码表示客户端缺少身份验证。通常,有效的登录将解决此问题。

  • 403 Forbidden:这个和401类似,但是在这种情况下,表示用户没有足够的权限。

  • 404 Not Found:表示在服务器中找不到资源。这是您在导航到不存在的页面时遇到的错误。

5xx – server errors

此范围表示服务器中存在处理错误。当发出 5xx 代码时,表示 服务器出现某种问题,无法从客户端修复。

该范围内的部分代码如下:

  • 500 Internal Server Error:表示服务器中的软件出现错误。没有更多信息被披露。

  • 501 Not Implemented:当客户端遇到尚未实现的端点时会发生此错误。

  • 503 服务不可用:当服务器由于某种原因不可用时发出此代码,无论是负载过大还是服务器停机。

Why HTTP codes matter in microservices

流行的说法不要重新发明轮子是我的最爱之一构建软件时的原则。 HTTP 代码是一种标准,因此每个人都了解不同代码的后果。

在构建微服务时,您始终需要记住,您的系统将与代理、缓存和其他已经使用 HTTP 的服务进行交互,以便它们可以根据服务器的响应做出反应。

最好的例子是断路器模式。无论您如何实现它以及使用什么软件,断路器都必须了解带有 500 代码的 HTTP 请求是错误的,因此它可以相应地打开电路。

一般来说,最好的做法是让您的应用程序的代码尽可能准确,因为从长远来看,这将有利于您的系统。

Summary


在本章中,您已经学习了如何构建安全软件(而不仅仅是微服务),尽管它的主题足够大,可以写一本完整的书。安全问题在于,公司通常将安全投资视为烧钱,但这远非现实。我是 80-20 法则的忠实拥护者:20% 的时间会给你 80% 的功能,而 20% 的缺失功能需要 80% 的时间。

在安全方面,我们确实应该以 100% 的覆盖率为目标;但是,本章中显示的 80% 将涵盖大多数情况。无论如何,正如我之前提到的,软件工程师应该了解最新的安全性,因为应用程序的安全性缺陷是杀死公司的最简单方法。

我们还一直在谈论可追溯性和日志记录,这是现代软件工程中最被忽视的主题之一,它变得越来越重要,特别是如果您的软件是使用微服务方法构建的。