
当涉及到网络上的身份验证时, 我们几乎都熟悉的密码-山之王, 臭名昭著的既缺陷, 也其无处不在。在密码之外, 您经常会遇到身份验证方案, 如令牌共享 (例如, oauth 或 rsa 令牌)。您很少看到的另一种方法是低级客户端证书形式的身份验证。客户端证书与更流行的服务器证书相关, 并且存在于相同的 tls 握手中。换句话说, 来自 web 浏览器的询问服务器是否具有 ssl 证书的初始连接也涉及服务器询问浏览器是否具有 ssl 证书。由于前者在 https 中的使用, 在过去十年中, 前者的采用意义重大。客户端证书仅对身份验证有用, 因此相比之下, 它们的采用比服务器证书更受限。尽管它缺乏受欢迎程度和一些繁琐的方面, 客户端证书是兼容的现代桌面浏览器和 web 服务器。对于某些用例, 为了 ssl 证书的绝对安全, 放弃密码的绝对便利是有意义的。
把这一切放在一起
我们需要掩盖一些丑陋的默认值, 但我不想因为早早获得太技术性而让你泄气。相反, 让我们向后工作, 看看这一切应该如何结合在一起。

上图您可以看到 firefox 的证书管理器具有存储个人证书的能力。使用客户端证书身份验证时, 用户的浏览器将具有来自受信任证书颁发机构的证书。图为, 你可以看到我有两个, 一个来自科莫多的电子邮件, 一个是认证的自签证书。稍后我们将进入自签名位, 但这并不代表与自签名服务器证书相同的风险。假设您的浏览器中有客户端证书 (您还没有, 但不要担心我们会回到该证书), 您的浏览器将自动将其发送到服务器时, 执行相同的 tls 握手用于协商 https。这是假设服务器实际上要求客户端证书。上述 nginx 配置是本网站配置的摘录。2-11 & 17 行几乎是您的基本 https 配置。我包括那些完整性, 因为和解 https 是客户端证书身份验证工作的先决条件;但是我真的希望你已经这么做了 第13、14和15行是服务器配置的真正肉类和土豆;
ssl_verify_client – 这是可选的, 这意味着我们要求浏览器提供客户端证书 (如果有), 并验证它 (如果提供), 但如果浏览器不提供 tls 握手, nginx 不会失败。
ssl_client_certificate – 此配置告诉 nginx 哪个证书颁发机构信任。与 web 浏览器维护受信任 ca 列表的方式非常相似, 这允许您的服务器具有类似的列表。nginx 将在握手过程中列出这些 ca 的名称。
ssl_verify_depth – 最后, 此设置让 nginx 知道允许允许多少个中间证书。客户端证书很少由证书授权它的根证书直接签名;因此, 将其设置为2容纳1个中间签名者, 除了根证书, 这是相当典型的。
上述三个配置元素是在 nginx 中启用客户端证书所需的全部元素。还有两个问题需要考虑;我们如何生成这些证书, 以及如何强加一种有意义的身份验证形式?让我们先讨论后者。
用户身份验证
要求浏览器提供证书是一回事, 实际对用户进行身份验证则是另一回事。在上一节中, 我们将 ssl_verify_client 设置为可选。这样做的理由是, 大多数网站都有公共和私人内容的混合体。您不希望仅仅因为用户缺少私有部分所需的证书就阻止他们访问公共内容。即使是完全私有的应用程序也会希望为那些未经过当前身份验证的应用程序 (例如登录或注册页面) 公开某种类型的自定义页面。出于所有这些原因, 建议将 ssl_verify_client 保留为可选逻辑, 并在应用程序或 nginx 位置规则中处理任何其他逻辑。
nginx 提供了两个可用于断言身份验证的变量-ssl_client_verify 和 ssl_client_raw_cert。
ssl_client_verify 变量, 在其最基本的形式, 等于 SUCCESS 时, 已提供客户端证书, 并匹配服务器的受信任的 ca 列表 (设置为 ssl_client_certificate)。您可以参考 nginx 文档中包含的其他值, 但对于大多数用例, 只需测试 SUCCESS 的相等或不平等就足够了。例如,


在这种情况下, 客户端证书身份验证仅提供额外的安全层。考虑到对 wordpress 网站进行的大量自动黑客攻击, 非常需要这样做。此安全措施还假定证书的简单验证足以过滤掉。这是因为 nginx 被配置为只信任一组证书颁发机构列表 (请参阅上面的 ssl_client_certificate 讨论)–在我的情况下, 我自己的 ca。
对于更精细的控制, ssl_client_raw_cert 变量允许检查客户端证书本身。此变量是 pem 编码的, 因此需要额外的处理才能使用。因此, 最好将此变量传递到上游服务器, 以便进行应用程序级检查。换句话说, 我们将此变量填充到 http 标头中, 并允许 ruby、node. js、. net、java 等在应用层中处理 pem 格式的证书, 并允许或拒绝请求。
上面的代码段说明了如何将这样的变量转发到上游服务器。按照约定, 以 “x” 开头的 http 标头是自定义的, 只要 nginx 和上游应用程序就同意命名约定, 就可以一次性自由选择我们想要的任何名称。
上面是该标头的内容将是什么样子的示例-pem 编码的证书。从现在开始, 如何处理事情完全取决于客户端应用程序。但是, 为了进行比较, 这在概念上与处理基于密码的身份验证没有太大区别。应用层应该有逻辑来验证提供的用户名和密码, 并做出相应的响应。同样, 使用客户端证书身份验证的应用程序应具有处理 pem 编码证书的逻辑, 提取它认为相关的任何字段 (很可能是通用名称), 并做出相应的响应。应用程序不需要验证证书, 因为在转发请求之前, 验证是由 nginx 处理的。
丑陋的细节
钥匙和证书–对大多数开发者说这些话, 他们就会畏缩。正确的是, 没有用户友好的方式很少–真正的功能超过形式的产物。爱他们, 或者恨他们, 他们是我们现代世界的必需品。在承受了几十年加密分析的压力后, 它们是最优先考虑的问题, 是确保数字辅助, 而不一定能让开发者的生活更轻松。正是出于这些原因, 我决定最后讨论这个话题。

如果您真的想跳过此部分, 请随时购买商业证书。像 comodo 这样的供应商出售客户身份验证票证, 价格大约是两人的晚餐价格–尽管这是一笔年费。除了成本, 商业证书的缺点是, 简单的验证与 ssl_client_verify, 像我们上面看到的, 是不那么实际。在这个例子中, 我展示了这个博客是如何单独使用证书的有效性作为看门人的。这适用于我的博客, 因为它是对我自己的证书颁发机构进行验证的。既然我不为每个人制作证书, 我就知道如果有人有一个真实的证书, 他们很有可能是我。
与服务器证书不同, 自签名客户端证书几乎没有缺点。自签名服务器证书的危险来自客户端信任方面。信任任何证书归结于信任一个或多个证书颁发机构安全、正确地颁发证书。浏览器信任证书颁发机构基于捆绑在每个浏览器中的经过深思熟虑的 ca 的管理列表。当遇到外观有效的证书时, 浏览器将拒绝连接, 如果不是来自受信任的 ca. 用户可以重写此行为, 但由于用户无意中信任错误证书的危险, 因此不鼓励这种行为。中间人攻击的可能性太大, 大多数网站不使用自签名的证书。危险就在这里。
如果分发得当, 自签名证书可以和商业证书一样安全, 但如果是服务器证书, 那就是一个很大的问题。对于客户的认证, 风险实际上是被排除的。浏览器不关心谁颁发其客户端证书, 因为他们同时保留公钥和私钥。服务器, 大概是由同一个人或组织操作的, 是自我签名的证书可以本质上信任自己–我希望。
我的时间足够停滞, 让我们看看如何生成这些证书。
第一步是, 我们必须成为证书颁发机构。使用 openssl 这相对容易。
首先, 检查 openssl 配置的 [ca _ default] 部分中的 dir 设置–您通常会在 /etc/ssl 中找到此设置。dir 设置应指向 ca, 使完整路径 /etc/ssl/ca -这就是这些示例将使用的路径。如果路径不匹配, 请更新 openssl 配置或以下脚本以彼此匹配。
验证后, 是时候创建我们的证书颁发机构了。
上面的脚本处理了所有的繁重工作。系统将提示您输入 ca 证书的密码-请记住这一点, 这不是你想忘记的。完成此一次性设置后, 我们现在可以创建一个或多个客户端证书了。这也可以自动转换为一个简单的脚本。
调用此脚本时, 应将客户端的用户名作为第一个参数提供。由于我们是自己的 ca, 这个用户可以是我们想要的任何东西–甚至是一个电子邮件地址。唯一的要求是它在我们自己的 ca 中是独一无二的。当创建用户脚本完成时, 您将有一个密码保护 pkcs #12 格式化的客户端证书准备导入到您的浏览器。你完了–继续导入它吧。在前面的部分中保护的 nginx 位置路径现在应该是可访问的。
在包装
客户端证书身份验证虽然不适用于所有方案, 但却是您可以使用的宝贵工具。通过将支持内置到现代桌面浏览器和 nginx 中, 设置可以在几分钟内完成, 但提供的保护远远优于常规密码。到目前为止, 这种技术最大的缺点是它在移动浏览器中缺乏支持, 以及与客户端证书的初始分发相关的麻烦。不要期望这将取代密码, 但它是为小型公司或个人网站在私人管理页面上增加安全性的理想选择。