首页
学习
活动
专区
工具
TVP
发布

Netflix如何解决身份验证问题?

本文最初发布于 Netflix 技术博客,由 InfoQ 中文站翻译并分享。

正如大多数开发人员可以证明的那样,处理安全协议和身份令牌,以及用户和设备身份验证都是很有挑战性的。想象一下,如果有多种协议、多个令牌、2 亿多用户和数千种设备类型,那么问题的规模可能会急剧扩大。

几年前,我们决定解决这种复杂性,并提出了一个新方案,最终形成一个新团队,我们将复杂的用户和设备身份验证以及各种安全协议和令牌的处理移到网络边缘,集中由一组服务和一个团队来管理。在此过程中,我们更改了服务网络中的端到端身份传播,从而使用一种密码验证的令牌无关的身份对象。

我们是如何做到的:

  • 降低服务所有者的复杂性,他们不再需要了解并负责终止安全协议和处理无数的安全令牌
  • 通过将令牌管理授权给具有领域专业知识的服务和团队来提高安全性
  • 提高审计能力和取证分析能力

我们是如何做到的

最初,Netflix 是一个允许会员管理他们的 DVD 队列的网站。后来,网站增加了流媒体内容的功能。流媒体设备出现的时间稍晚一些,但这些设备最初的能力有限。随着时间推移,设备的性能和功能不断提高,以前只能在网站上访问,现在可以通过流媒体设备访问。Netflix 服务的规模迅速增长,支持超过 2000 种设备类型。

现在,支持这些功能的服务需要理解多个令牌和安全协议,以便识别用户和设备,并授权对这些功能的访问,这增加了服务的负担。整个系统相当复杂,并且开始变得脆弱。此外,边缘层的架构正在演变为 PaaS(平台即服务)模型,我们需要做出一些艰难的决定,包括如何以及在何处进行身份令牌处理。

复杂性:多个服务处理认证令牌

为了说明系统的复杂性,下图展示了在做出本文介绍的更改之前,用户的登录流程:

从大的方面说,这个(大大简化的)流程涉及如下步骤:

  1. 用户输入他们的凭据,Netflix 客户端将凭据连同设备的 ESN 一起传送到边缘网关,即 Zuul。
  2. Zuul 将用户调用重定向到 API/login 端点。
  3. API 服务器编排后端系统来验证用户。
  4. 如果验证成功,API 服务器就向上游发送一个 cookie 响应,包括 customerId(一个 Long 值)、ESN(一个字符串)和一个过期指令。
  5. Zuul 将 cookie 发回给 Netflix 客户端。

这个模型有一些问题,比如:

  • 外部有效的令牌是在验证栈的深处生成的,需要一直向上传播,这可能导致记录不当或管理不当。
  • 上游系统必须重新打开令牌来识别用户登录,并可能管理多个并行的身份数据结构,这些数据结构很容易出现不同步。

多个协议 &令牌

上面的示例显示了一个处理一种协议(HTTP/S)和一种令牌类型(cookie)的流。Netflix 流媒体产品中使用了多种协议和令牌,汇总如下:

这些令牌被 Netflix 流媒体生态系统中的多个系统消费,并可能被它们更改,例如:

让情况变得更复杂的是,有多种方法可以将这些令牌或其中包含的数据从一个系统传输到另一个系统。在某些情况下,令牌被破解,身份数据元素被提取为简单的原语或字符串,用于 API 调用,或通过请求上下文头从一个系统传递到另一个系统,甚至作为 URL 参数。系统中没有适当的检查来确保令牌或其中包含的数据的完整性。

Netflix 的规模

与此同时,Netflix 的经营规模呈指数级增长。如今,Netflix 拥有 2 亿多订阅用户,每月有数百万活跃设备。我们每秒处理超过 250 万个请求,其中很大一部分请求需要某种形式的身份验证。在旧有的架构中,每个请求都会导致一个 API 调用来进行验证,如下所示:

EdgePaas 半路杀出

使情况进一步复杂化的是,Edge 工程团队正在从旧的 API 服务器架构迁移到新的基于 Paas 的方法。当我们迁移到 EdgePaaS 时,前端服务从基于 Java 的 API 移到了 BFF(为前端应用开发的后端),也就是 NodeQuark,如下图所示:

这个模型使前端工程师能够在核心 API 框架之外拥有和操作他们自己的服务。然而,这又带来了另一层复杂性——这些 NodeQuark 服务将如何处理身份令牌?NodeQuark 服务是用 JavaScript 编写的,终止像MSL这样复杂的协议既困难又浪费,因为要复制令牌管理的所有逻辑。

让我们总结一下

总之,我们发现,我们用于处理大规模的身份验证和身份令牌的解决方案复杂而低效。我们有多种身份令牌类型和来源,每种都需要特殊处理,其逻辑被复制到不同的系统中。关键身份数据以不一致的方式在整个服务器生态系统中传播。

边缘身份验证服务来救场

我们意识到,为解决这个问题,需要一个统一的身份模型。我们需要在更上游的地方处理身份验证令牌(和协议)。为此,我们将身份验证和协议终止移到网络边缘,并新建了一个具有完整性保护的、令牌无关的身份对象,它可以在整个服务器生态系统中传播。

将身份验证移到边缘

考虑到我们的目标是提高安全性和降低复杂性,并最终提供更好的用户体验,我们的策略是将设备身份验证操作、用户身份识别和身份验证令牌管理集中到服务边缘。

从大的方面来说,Zuul(云网关)将成为令牌检查和有效负载加密/解密的终止点。如果 Zuul 无法处理这些操作(一小部分),比如令牌不存在,需要更新,或者无效,Zuul 会将这些操作委托给一套新的边缘身份验证服务来处理加密密钥交换和令牌创建或更新。

边缘身份验证服务

边缘身份验证服务(EAS)既是一种将设备和用户身份验证和识别移至云边缘的架构概念,也是一套为处理每种令牌类型而开发的服务。

在功能上,EAS 是一系列运行在 Zuul 中的过滤器,它可以调用外部服务来支持它们的域,例如,调用一个服务来处理 MSL 令牌,或调用另一个服务来处理 Cookie。EAS 还涵盖了令牌的只读处理(稍后详细介绍),以便用于创建 Passport。

EAS 处理请求的基本模式如下:

对于进入 Netflix 服务的每个请求,Zuul 中的 EAS 入站过滤器检查设备客户端提供的令牌,并将请求传递给 Passport 注入过滤器,或委托给一个边缘身份验证服务来处理。Passport 注入过滤器生成一个与令牌无关的身份,向下传播到服务器生态系统的其余部分。在响应路径上,EAS 出站过滤器根据需要,在边缘身份验证服务的帮助下,确定并生成发送回客户端设备所需的令牌。

现在,系统的架构形式如下:

注意,令牌从未越过边缘网关/ EAS 边界。MSL 安全协议在边缘终止,所有令牌都被破解打开,身份数据通过服务器生态系统以与令牌无关的方式传播。

关于弹性的说明

在基本路径上,Zuul 能够处理大部分有效且未过期的令牌,而边缘身份验证服务处理其余的请求。

根据设计,EAS 服务有容错性,例如,当 Zuul 识别出 Cookie 有效但已经过期时,对 EAS 的更新调用会失败:

在失败的情况下,Zuul 中的 EAS 过滤器将放宽限制,允许传播已解析的标识,并指示在下一个请求上重新安排更新调用。

令牌无关的身份(Passport)

容易发生变化的身份结构是无法满足需求的,因为这意味着从一个服务传递到另一个服务时可信身份信息会减少。因此,需要一个与令牌无关的身份结构。

我们引入了一个名为“Passport”的身份结构,它允许我们以统一的方式传播用户和设备的身份信息。Passport 也是一种令牌,但是使用不同于外部令牌的内部结构有很多好处。不过,下游系统仍然需要访问用户和设备标识。

Passport 是在每个请求的边缘创建的短期身份结构,也就是说,它的作用域是请求的生命周期,它完全是在 Netflix 生态系统的内部。它们是通过一组身份过滤器在 Zuul 中生成的。Passport 包含了用户和设备的身份信息,是 protobuf 格式的,并受到 HMAC 的完整性保护。

Passport 结构

如上所述,Passport 被建模为协议缓冲区。Passport 的定义如下:

message Passport {
   Header header = 1;
   UserInfo user_info = 2;
   DeviceInfo device_info = 3;
   Integrity user_integrity = 4;
   Integrity device_integrity = 5;
}

Header 元素传递创建 Passport 的服务的名称。更有趣的是,传播的内容与用户和设备有关。

用户 &设备信息

UserInfo 元素包含识别请求发起用户的所有信息,DeviceInfo 元素包含用户访问 Netflix 所使用设备的必要信息:

message UserInfo {
    Source source = 1;
    int64 created = 2;
    int64 expires = 3;
    Int64Wrapper customer_id = 4;
        … (some internal stuff) …
    PassportAuthenticationLevel authentication_level = 11;
    repeated UserAction actions = 12;
}
message DeviceInfo {
    Source source = 1;
    int64 created = 2;
    int64 expires = 3;
    StringValue esn = 4;
    Int32Value device_type = 5;
    repeated DeviceAction actions = 7;
    PassportAuthenticationLevel authentication_level = 8;
        … (some more internal stuff) …
}

UserInfoDeviceInfo都携带请求的SourcePassportAuthenticationLevelSource列表是请求的分类,包含使用的协议和用于验证请求的服务。PassportAuthenticationLevel是我们放在身份验证声明中的信任级别。

enum Source {
    NONE = 0;
    COOKIE = 1;
    COOKIE_INSECURE = 2;
    MSL = 3;
    PARTNER_TOKEN = 4;
        …
}
enum PassportAuthenticationLevel {
    LOW = 1; // untrusted transport
    HIGH = 2; // secure tokens over TLS
    HIGHEST = 3; // MSL or user credentials
}

下游应用程序可以使用这些值来做授权和/或用户体验决策。

Passport Integrity

Passport 的完整性通过 HMAC(基于哈希的消息验证码)来保护,HMAC 是一种特定类型的 MAC,涉及加密哈希函数和秘密加密密钥。它可用于同时验证消息的数据完整性和真实性。

用户和设备完整性定义为:

message Integrity {
    int32 version = 1;
    string key_name = 2;
    bytes hmac = 3;
}

Integrity 元素的版本 1 对 HMAC 使用 SHA-256,它被编码为 ByteArray。Integrity 的未来版本可能会使用不同的哈希函数或编码。在版本 1 中,HMAC 字段包含来自 MacSpec.SHA_256 的 256 位的值。完整性保护可以保证 Passport 的字段在创建后不会发生变化。在使用其中包含的任何值之前,客户端应用程序可以使用 Passport Introspector 检查 Passport 的完整性。

Passport Introspector

Passport 对象本身是不透明的;客户端可以使用 Passport Introspector 从报头中提取 Passport,并检索其中的内容。Passport Introspector 是 Passport 二进制数据的封装器。客户端通过工厂创建 Introspector,然后访问基本的访问器方法:

public interface PassportIntrospector {
    Long getCustomerId();
    Long getAccountOwnerId();
    String getEsn();
    Integer getDeviceTypeId();
    String getPassportAsString();
    …
}

Passport Action

在上面的 Passport 协议缓冲区定义中,还包含 Passport Action 的定义:

message UserInfo {
    repeated UserAction actions = 12;
        …
}
message DeviceInfo {
    repeated DeviceAction actions = 7;
        …
}

Passport Action 是下游服务在对用户或设备标识执行更新时显式发送的信号。EAS 使用该信号来创建或更新相应类型的令牌。

再谈登录流

让我们以一个所有这些解决方案一起工作的例子来做下总结。

随着身份验证和协议终止移到边缘,以及 Passport 作为身份标识的引入,前面描述的登录流已经演变为下面这个样子:

  1. 用户输入他们的凭据,Netflix 客户端将凭据连同设备的 ESN 一起传送到边缘网关,也就是 Zuul。
  2. 运行在 Zuul 中的身份过滤器生成一个设备绑定的 Passport,并将其传递给 API/login 端点。
  3. API 服务器将 Passport 传播到负责对用户进行身份验证的中间层服务。
  4. 在成功地验证所提供的声明后,这些服务创建一个 Passport Action,并将其与原始的 Passport 一起发送到上游的 API 和 Zuul。
  5. Zuul 调用 Cookie 服务来解析 Passport 和 Passport Action,并将 Cookie 发回给 Netflix 客户端。

主要的好处和经验

简化了授权

外部令牌流入下游系统的原因之一是,授权决策通常依赖于令牌中的身份验证声明以及与每种令牌类型相关联的信任。在我们的 Passport 结构中,我们为这种信任分配了级别,这意味着需要做授权决策的系统可以围绕 Passport 编写合理的规则,而不是在多个服务的代码中复制信任规则。

显式、可扩展的身份模型

规范化的身份结构是非常有用的。传递标识原语的方案很脆弱,也很难调试。如果调用链中的客户身份从服务 A 到服务 D 更改了,是谁更改的呢?一旦身份结构通过所有关键系统,添加新的外部令牌类型、新的信任级别或表示身份的新方法就相对容易了。

操作关注点和可见性

有了一个结构(如 Passport),就可以定义服务写 Passport,并定义其他服务验证它。当 Passport 在传播时,我们可以在日志中看到它,打开它,并验证它,从而知道其身份是什么。我们也知道 Passport 的来源,可以追溯到它在哪里进入的系统。这使得调试任何与身份相关的异常变得更加容易。

减少下游系统复杂性 &负载

将统一的结构传递给下游系统,意味着这些系统可以使用内省库轻松地查找设备和用户标识。它们可以使用公共结构,而不是单独处理每种类型的外部令牌。

通过将令牌处理从这些系统转移到中心化的边缘身份验证服务,下游系统在 CPU、请求延迟和垃圾收集方面的指标有了显著改善,所有这些都有助于减少集群占用的空间和云成本。以下这些收益的示例来自主 API 服务。

在之前的实现中,每个请求需要两次解密/终止成本,因为我们需要有在边缘路由的能力,但也需要在下游服务中有多样性的协议终止。一些性能改进就是由于这种整合——现在只需要处理一次 MSL 请求。

CPU 与 RPS 比值

卸载令牌处理导致每个请求的 CPU 成本减少了 30%,平均负载减少了 40%。下图为 CPU 与 RPS 的比值,越低越好:

API 响应时间

API 服务上所有调用的响应时间都有了显著的改善,平均延迟减少了 30%,99 百分位延迟减少了 20%:

垃圾收集

API 服务也显著减少了 GC 压力和 GC 暂停时间,如 Stop The World 垃圾收集指标所示:

开发速度

将这些身份验证和身份相关的问题从微服务的开发人员身上抽离出来,意味着他们可以专注于自己的核心领域。这个领域的更改现在只需在一组专门的服务中完成一次,而不是分布在多个服务中。

未来展望

(更)强大的身份验证

我们目前正在扩展边缘认证服务,以通过一个名为“Resistor”的新服务支持多因素认证。基于机器学习模型,我们有选择地引入了针对可疑连接的第二个因素。当引入新的流程时,我们也会引入新的因素,例如,发送到电子邮件或手机上的一次性密码(OTP)、向移动设备推送通知和第三方身份验证程序。对于希望增加账户安全性的用户,我们也在探索可选的多因素身份验证。

灵活的授权

现在,既然已经有了流经系统的经过验证的身份,我们可以使用它作为授权决策的强信号。去年,我们开始探索一种新的产品访问策略(PACS),目前正致力于将其投入生产,为 Netflix 流媒体产品提供一些新的体验。最近,PACS 为Streamfest(Netflix 在印度的免费周末)提供了体验访问控制。

原文链接:

https://netflixtechblog.com/edge-authentication-and-token-agnostic-identity-propagation-514e47e0b602

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/7RQ3rDHPOI6h2LZ5yzg7
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券