授权是在应用中控制谁可以做什么的机制。 是你保证用户可以访问他自己所拥有的数据,而不允许查看不属于他数据的方式。 有一套通用授权架构模式其适应任何应用架构 —— 了解这些模式可以让你编写授权代码更加容易。 本指南将教给你这些模式。

授权是每个应用中非常关键的一部分,但它几乎不会被用户察觉到。 它通常也和你正在处理的特性和业务逻辑无关。

认证(Authentication)与授权(Authorization)

许多人经常使用术语 ”auth“ 来指代认证和授权,他是应用程序安全中稍微有些重叠的机制。 但他不是一回事。

认证是验证用户身份的机制。 他是应用程序的大门。 例如用户名和密码共同构成身份(identity)(用户名)和认证方式(你知道密码吗) 其他形式的认证方式包括 OAuth 以及 OpenID Connect(OIDC)(它们通常用来添加”使用谷歌登录“或”使用脸书登录“等功能), 以及 SAML 这是企业用来为员工提供跨应用单点登录的标准。

授权是控制用户可以做什么的机制。 如果认证是大门,那授权就控制你进来之后可以打开那些门。

授权通常构建于认证之上,并且当以用户是谁来决定其可以做什么时,两者最为接近。 比如,一旦用户认证通过,我们可以用他的用户名来查看他有那些权限, 或者我们可以通过一些我们可以获取到的其他属性推断她的权限。

本教程聚焦于授权,并且只在必要时会涉及到认证。 我们假设你有一个或者可以通过其他地方的指南部署一个认证系统。

授权在应用中看起来是什么样的

我们想向你展示现实生活中的授权示例,而不仅仅是理论上的。 为了做到这一点,我们构建一个可以在其中演示每个概念的示例应用程序。

我们的这个应用叫 GitClub,一个用来进行源代码托管与协作与版本控制的网站。 它和现实生活中的 GitHub 和 GitLab 等 Git 托管平台非常类似。 GitClub 提供了一个纯粹的例子来说明最初是什么催生了授权 —— 保护对资源的访问。 GitClub 中的一种资源是仓库,并且仓库是访问受限的。 用户可以或不能去读取或修改一个仓库。 这意味着我们需要授权。

网站架构

基于 GitLab 人员提供的其产品机构的出色文档,我们的服务将从一个恰当的架构开始。 如果这看起来很复杂 —— 不要担心! 当我们谈到架构中的授权时,我们将会逐层剥开复杂的地方。

GitClub Architecture

上面的图表包含我们架构的主要部分。 gitclub.dev 会处理三种不同类型的流量:

  • 浏览网站。这将为你的浏览器返回 HTML,就像 gitclub.com 的前端页面一样。

  • API 请求。这将处理来自用户或第三方集成发送来的请求并返回 JSON 或其他格式的数据。

  • Git 连接,与你克隆 https://gitclub.dev/oso/authzacademy 类似。这可以是 SSH 或 HTTP 连接。

所有的请求都连接到一点:代理,之所以这样命名是因为它代表(”代理“)网络服务器。 代理会将连接重定向到正确的位置。 在一个完整的系统中,代理可以由许负载均衡认证代理等多个部分组成。

Website 和 API 可以访问(DB),其中保存了用户账户数据等信息。

API 和 Git 连接将会访问存储 Git 仓库的文件系统(FS)。

对于认证(提醒:请参阅认真与授权简介),我们的 Web 应用将使用简单的用户名和密码的机制来处理用户身份验证。

在初始身份验证后,Web 应用将会向用户返回一个令牌,以便在后继的请求中他不再需要再次提交密码。 Web 应用还允许用户创建 API 令牌用于 API 应用与之交互。 这些令牌都是 ”Bearer tokens“ —— 授予令牌持有者访问权限的轻量级令牌。

Git 服务与 Web 应用使用相同的用户名和密码对进行身份验证。

旁白: 单体 VS 微服务

我们示例的架构非常单体。 我们只有几个应用在运行。 对于微服务,我们会在遇到它们时之处一些差异,我们将在第六章:认证与微服务中进行深入探讨。

数据模型

在系统中,我们有许多 Git 仓库

组织 允许分组对仓库的访问。 一个组织(比如一个公司,或一个开源工作组)会拥有许多仓库。 每个组织可以拥有许多用户。

一个人被表示为一个 用户 。 一个用户可以是多个组织的成员。

我们的授权目标

跨越我们的系统,我们想要执行一个授权规则: 只有当一个用户是拥有仓库的组织的成员时才可以访问仓库。

我们将会在之后添加更多规则!

在那里放置我们的授权逻辑

现在我们概述了我们的架构,让我们想一下在请求通过我们的应用程序组件时可以在那里进行授权,看看会发生什么。 我们的一个 GItClub 用户,”alice@acme.org“ 通过 Web 界面访问应用程序。 假设他尝试某一个特定仓库的页面,比如:https://gitclub.dev/acme/anvil。 会发生什么?

他之前使用密码进行了身份认证,所以请求将携带令牌作为其凭据。 在这种情况下,我们的用户被允许访问这个仓库,因为 acme 是一个组织并且当前用户是这个组织的成员。

当我们通过基础设施跟踪请求时,我们将坚持授权的三大原则:

  • 是谁发起的这个请求?

  • 他要做什么?

  • 他要对什么进行操作?

初始化连接

首先,用户的浏览器与我们的外部代理建立连接。

是谁发起的这个请求?

我们还不知道发出请求的用户的身份。 要做到这点我们需要将令牌从请求中取出。 或许我们知道他的IP地址。

他要做什么?

开启 TLS 连接。

他要对什么进行操作?

主机: gitclub.dev 的 443 端口。

我们还没有进行应用授权,但可以在这里做一些网络级别的授权。 可能我们有一个IP地址的放行列表或者甚至需要双向 TLS(虽然这https://twitter.com/colmmacc/status/1057017343438540801[有些争议])。

在代理中

代理被配置为根据需要将流量重定向到 Web、API 和 Git 服务器。 这三者最终都需要进行一定的用户身份认证。 正如我们之前所说,认证是用户名/密码对或 Bearer 令牌。

如果我们有许多下游服务需要进行身份认证,那么添加身份认证代理会更有价值。 例如,Web 应用和 API 应用都需要令牌才能访问大多数路由。 如果我们在代理中处理令牌,那么我们可以执行以下授权:

是谁发起的这个请求?

代理验证请求中的令牌,其中可能含有用户有关的信息。 例如,我们可能使用和 JSON Web Token(JWT)中非常相似的 JSON 编码的数据。

假设我们解码后的令牌像这样:

令牌结构

然后我们知道了用户是”alice@acme.org“。

他想做什么?

通过检查 HTTP 请求,我们可以了解到用户发出了一个 GET 请求。

他要对什么进行操作?

再一次通过检查 HTTP 请求,URL 是 /acme/anvil。

我们可以根据我们所知道的进行授权吗?

这取决于我们想要执行什么样的授权。 只提供请求中存在的信息,我们只能进行路由级别的授权。 是否允许 “alice@acme.org” 向 /acme/anvil 发出 GET 请求?

关于用户我们唯一的信息是他的邮箱地址。 所有用户可以向 /<owner>/<repository> 这种形式的路径发起 GET 请求,所以请求是允许的。

但如果我们想要强制执行所有要求 —— 用户必须和仓库在同一个组织中 —— 那么我们没有足够的信息进行判断。 怎样才能获得这些信息? 我们可以考虑像令牌中添加越來越多的信息。 或者我们可以配置我们的代理去访问数据库。 这种方法为代理引入了相当的复杂性并重复了数据库访问的逻辑。

但是代理是解决授权临近问题的理想选择之处,例如限制用户访问频率、 需要 API 密钥或进行认证以及扫描可能你会在 Web应用防火墙(WAF)中发现的恶意负载。

在应用路由中

GitClub 架构高亮 Website 和 Database

我们已经介绍了代理! 让我们转到网站和数据库。

最终,我们经过认证的请求到达 Web 应用的路由,由路由决定如何进行处理此请求。 此时通常将请求中间件中提供的身份转换为从数据库获取的数据模型。

是谁发起的这个请求?

假设认证中间件已经将请求中提供的用户身份转换为用户对象, 这让我们可以访问我们想要知道的用户有关的所有信息。

他想做什么?

我们仍然在使用 HTTP 请求对象,并且 HTTP 方法是 GET。

他要对什么进行操作?

这里我们仍旧只有请求对象和路径:“/acme/anvil”。

由于我们现在可以访问应用程序数据,所以我们可以查看相关信息。 例如,我们可以使用现有逻辑来查找 /acme/anvil 对应的组织, 判断我们的用户是否属于改组织。

然而,这几乎正是我们下一步在控制器层需要做的事情。 控制器将接受请求,查询数据,执行任何必要的数据操作并应用业务逻辑。

所以,如果我们在中间件中通过这种方式进行授权, 我们最终会重复逻辑并有可能会产生对数据库的重复调用。

有一些场景在这一层进行授权是有意义的:

  1. 通过检查一些简单的路由级属性,将深度防御方法应用于授权。 例如,网站有一个仅限管理员的域 /admin/…​ 并且管理员权限存储在用户对象中, 那么我们就可以快速进行这种检查。

  2. 确保在处理请求的某个地方进行了授权,可能在应用的更深层。 这样做获得了一些好处,这样做可以确保在任何地方都进行授权, 而不会受只涵盖粗略的路由级别问题的限制。

  3. 在请求路由和数据访问紧密偶和的应用程序中。 例如,在数据库前具有精简 REST 或 GraphQL 接口的 API 驱动应用。 在这种情况下,路由+控制器层被有效的压缩到一层中,并且授权可以同时应用到两者。 接下来还有更多。

在 Web 应用/控制器中

Web 路由将 GET /acme/anvil 映射到我们的控制器方法上。 假设这个方法是 view_repository(owner: "acme", name: "anvil")。

在这个方法中,我们负责整理现实 /acme/anvil 网页所需的数据并将这些数据传给视图。 在 GitClub 中,我们喜欢坚持书写简单明了的代码,所以我们对我们的页面进行简单的服务端渲染。 所以我们获取 UI 所需的所有数据并为用户渲染模板。

在不设计太多细节的情况下,我们的仓库视图大致包括一些源码、问题、合并请求、贡献者等,以及一些仓库自身相关的基础信息。

最终向用户展示的所有数据都应该获得授权。 他能首先看到存储仓库吗? 页面上的所有其他附加数据那?

是谁发起的这个请求?

在上一步中,我们可以假设用户对象已经被获取为认证中间件的一部分了。

他想做什么?

查看仓库页面并访问仓库信息。 在控制器方法中,我们拥有用户正在做什么的完整上下文。

他要对什么进行操作?

无论如何我们从数据库检索出仓库 acme/anvil 都是方法的一部分。 最终,我们就像拥有了我们一排的所有鸭子。 我们拥有所有需要的数据并且我们精确的知道用户想要做什么。 anvil 仓库属于 acme 组织,并且当前用户是这个组织的成员, 所以是的!我们应该让他读取这些数据。

Extra credit

稍等,还有!

那用来渲染视图的所有辅助数据那? 仓库成员、问题、提交等。 所有这些都显示给用户吗?

好吧,我们确实可以根据需要进一步进行访问控制决策。 我们可以访问所有相关信息并且我们知道我们尝试代表用户获取那些数据。

在这里我们还可以做的更多。 假设我们知道用户没有权限来配置仓库的设置,因为他不是仓库的管理员。 也就是说我们可以把这一信息返回给视图渲染器,这样渲染器就可以在页面上隐藏相关的选项了。 我们会在后边的章节中介绍更多相关内容。

如果用户没有查看仓库的权限,那么我们也处在一个渲染一个视图来显示帮助信息或返回一个 “Forbidden” 响应的合适位置。

在数据库/数据库连接中

这是我们最后一个可以进行授权的地方了: 当从数据库获取数据时。 在此操作的几个地方我们可以考虑授权:通过 SQL 请求自身(或许通过一个中间件或查询代理),甚至可能在数据库中。

是谁发起的这个请求?

我们可能会在查询上下文中包含用户相关信息。

他想做什么?

执行一个 SQL SELECT 语句。

他要对什么进行操作? * 他这对什么进行操作?

使用 name = "acme/anvil" 过滤仓库表。

我们现在处于数据访问层 所以我们需要在数据库连接/请求或数据库查询中包含任何需要的应用上下文。

记住我们想要强制执行的逻辑:

如果用户和仓库归属于同一组织,那么用户可以读取仓库。

数据访问层是执行此操作的理想位置因为我们的规则可以被轻松的表示为一个 SQL 请求。 在这种情况下,当在仓库表上执行一个 SELECT 语句,我们可以和组织成员表进行关联并且只选取和用户在同一组织下的仓库。

例如,如果原始请求像下面这样:

仓库表上的 SELECT 语句

然后应用授权后会变成这样:

仓库表上带表关联的 SELECT 语句

这种方式的优点是他允许你不知是回答是/否授权请求。 例如,你可以使用同样的过滤器列出一个用户可以在其主页上看到的所有仓库。

这里的挑战是如何生成用于授权的查询过滤器,而不需要重复构建和管理 SQL 语句的逻辑, 否则我们可能会在应用程序中处理这些逻辑。 因此,通过使用应用中其他地方的相同机制来实现授权过滤器是最有意义的。

总结

总而言之,对于在何处进行授权有几个可选项:

  • 在网络层。 这一层拥有的数据非常有限并且只允许进行简单的网络访问控制比如允许/拒绝列表。 我们不应该这里这里专注于授权。

  • 在代理或路由中。 这对于路由级搜全是最好的,因为更细粒度的访问控制通常需要额外调用服务或数据库来做出授权决定。

  • 在应用程序/控制器中。 在这里我们可以使用所有信息,所以我们可以轻松应用我们的授权要求。 这是放置我们授权逻辑的理想位置。

  • 在数据库中: 如果应用生成数据库过滤器,我们可以将我们的授权应用在这里。 这让我们可以就访问控制提出更广泛的问题,所以如果可能的话,这里是执行强制授权的最佳位置。

将授权应用到尽可能接近我们要保护的资源是最简单的,因为这里拥有也嗯胡想要做什么最精确的上下文以及用于做出决定的数据。

添加授权到一个应用中

我们已经讨论过要在架构中的那个位置应用授权。 我们看到在许多案例中我们想要在应用中进行授权,这样做我们可以完全访问应用的上细问。

但是我们如何设计、实现并执行授权那?

天真的方式

当许多人开始时,他们不会将“实现授权”作为单独的开发步骤。 他们只是按需在他们的应用代码中添加看上去需要的检查。 这种情况如此普遍的原因是因为逻辑在才开始的时候感觉很简单。 甚至你都不会注意到它。 例如,你可能使用用户 ID 或仓库 ID 作为数据库查询的一部分来过滤所有仓库。 因为应用程序上下文就在那里,所以很容易以这种方式实现它。

这很快就会变得困难。 随着你需要进行授权的地方数量的增加,最终你会重复相同的逻辑。 之后再进行任何修改都需要我们记住我们重复逻辑的每个地方。

在我们之前的例子中,我们限制用户只有当其是 Acme 组织的成员时才能在页面上看到 /acme/anvil 仓库 。 同样的逻辑适用于 /acme/anvil/issues/acme/anvil/members 等子页面。 处理这些页面的每个方法都需要重复相同的逻辑。

现在假设我们添加一个允许邀请组织外部的用户在特定仓库上进行协作的新功能。 我们现在需要将这一检查添加到我么的授权处理程序中,在这一天真的案例下这意味着需要修改每个处理仓库页面的方法。

从我们的应用中分离授权是非常困难的

就像任何其他应用程序逻辑一样,我们希望应用 “关注点分离”,并独立于应用逻辑之外编写我们的授权规则。 事实证明这非常困难! 最开始,可能我们重构所有的仓库视图逻辑为先加载并检查仓库的权限。 但是后来我们意识到我们的身份验证规则不是通用的:只有组织管理员被允许访问 /acme/anvil/settings ,所以我们需要修改我们的仓库抽象来包含与组织管理员有关的信息。 授权与应用如此紧密的交织在一起,以至于很难相处一个干净的界面来将授权与业务逻辑分开。

我们怎样才能设计出更好的东西?

形式化我们的授权模型

我们在上面的示例部分中看到,我们通常可以将授权决策构建为:

  • 是谁发起的这个请求?

  • 他要做什么?

  • 他要对什么进行操作?

在关于授权的书面用于中,它们每一个都有一个正式术语。

“谁”被称为 actor 。 在许多用例中,actor 只是应用的一个用户。

“他在尝试做什么”通常归结为一个简单的动词。 例如,create、read、update、delete(CRUD)这在 API 中很常见。 我们称这些为 动作

“他要对什么进行操作”指资源。 这可以是应用中的特定对象 —— 在 GitLab 中,可以是仓库或组织等。

这个三元组(有各种名称)在授权系统中经常使用。 例如,在 Microsoft Azure 的文献中使用“Security Principal”,“Action”,“Resource”。

将这种结构引入您的应用程序的好处是双倍的。 首先你可以使用一致的语言来讨论授权,不论是简单的还是复杂的用例。 Actor 可以是简单的用户,但也可以是表示一个将自己权限代理给另一个用户的用户的第三方应用程序。

其次,其提供了一个干净的界面作为开始。 一个简单的授权接口接受一个三元组(Actor、Action、Resource)并对输入返回允许或拒绝的授权决议。

我们的授权 API 使用什么接口

到目前为止,我们主要关注的是我们可以在那里“应用”授权, 这指的是评估输入请求、提取相关信息、将其与附加数据查找相结合、实施规则和检查的整个过程,甚至包括按权限过滤数据。

其中包含两个重要部分:执行和决定。 授权接口是这两部分的界限。

假设我们的接口是方法 is_allowed(actor, action, resource) ,他会被我们之前谈到的输入调用,例如, is_allowed(current_user, "read", Repository(name: "acme/anvil"))

施行是我们决定如何处理授权决议的方式。 这意味着要像我们之前的示例中看到的那样从请求中提取 actor、action 以及 resource 并调用 is_allowed 方法。

在 GitClub 中,如果用户没有读取 /acme/anvil 存储库的权限,我们可以使用 HTTP 403 Forbidden 响应进行响应,或者将用户重定向到不同的页面。 其他执行相关的例子包括使用数据库过滤器来限制对整个数据集合的访问。

决策是我们如何实现认证接口: 给定输入的三元组(actor, action, resource),我们返回一个结果。 在我们之前的示例中,决策是:允许用户访问此仓库,因为 acme/anvil 在 acme 组织中,并且用户是改组织的成员。

决策不一定需要是二进制的是/否,但可能进一部依赖与其他时间或检查,或者具有其他影响,比如发出一个警告。

执行与决策架构

当我们谈到需要在应用程序或数据库层进行授权时,这主要是指执行授权。 我们将在未来的指南中介绍有关执行授权的选项。 但决策是一个单独的部分并且可以在不同的地方实现。

实现授权决策的可选项

做出授权决策需要两块信息,数据和逻辑:

  • 授权数据 是应用数据中用于访问控制的一个子集。 Alic 是 Acme 组织的成员并且 acme/anvil 仓库属于 acme 组织。

  • 授权逻辑 描述了在数据上表达的抽象规则,用来确定是否允许一个用户在资源上进操作。 例如,一个组织的成员被允许访问属于此组织的仓库。

有两种进行授权决策的不同形式:集中式和分散式。 在集中式中,授权决策被代理给一个可以提供对必要数据进行访问并将授权逻辑作为输入的中心权威。 在分散式中,应用使用已经可访问的数据自己进行授权决策。

还有第三种方法,混合式,他采用分散式,但使用于有多个服务和应用的系统。

我们将在这里结合实际案例介绍这三种方案。

分布式授权:保持授权在应用中

从幼稚的临时实现自然发展则是转移到程序中内置的授权系统。 这可能看起来像重构已有代码以便通过我们之前提出的接口将执行和决策分开。 这可能是一个基于你所用框架的内置部分或转用授权库专门构建的 DIY 实现。 例如,GitLab 的 DeclarativePolicy 框架、用于 Ruby/Rails 的 Pundit、用于 Python/Django 的 django-guardian,又或是(我们的最爱,因为我们写了它)跨语言的Oso。 无论使用那种方式,这种方法都有一些优点和缺点。

首先,将授权逻辑保留在应用程序中,而不是分离为单独的服务,这简化了开发体验。 添加到你的应用程序中仅仅意味着包含相应的代码或库。 修改权限和修改程序一样,并会经历同样的过程。 这延伸到其他领域,比如测试和调试 —— 这些都像应用程序代码一样发生。

但,在应用中授权最显著的又是可能是可以访问数据来进行决策。 在我们的例子中,进行一个决策需要检查一个用户是否属于某一特定组织。 如果应用程序已经使用了这些数据 —— 比如我们的 Web 应用已经知道了怎样列出用户的组织 —— 那么我们已经有了检索这些信息并做出决策的机制。

一个授权决议需要检查授权逻辑和授权数据

在分散式授权中,所有应有需要有必要的逻辑和数据来进行授权决策。

另一方面,当我们有多个应用程序时 —— 比如我们的 Web、API 和 Git 应用 —— 那么我们最终可能会在所有应用程序中复制同样的授权逻辑。 更糟糕的是,我们最终可能会复制获取数据的逻辑。 除了授权决策之外,我们的 Git 应用不需要了解组织。

中心式授权:添加一个服务

如果我们有许多应用或服务处理相投的参与者、操作和资源,我们不应该在他们之间复制我们的授权代码。 我们应该通过添加一个分离的服务来解耦我们的授权逻辑。

授权数据流

在中心化模型中,应用管理自己的数据,逻辑与应用解怄,但中心服务仍然需要访问所有数据。

中央服务有几个不同的选项,这中医药和他们访问应用程序数据的方式有关。

首先可以将中央服务构建为常规服务,通过向其他已有服务发送请求获取数据。 这种方式的缺点是他在中央服务和所有其他服务之间直接或间接建立了耦合。 这是一个问题,例如,如果我们需要修改中央服务器的 API —— 那么所有其他服务都需要进行相应修改。

相反,我们可以让服务成为数据的所有者。 一些生产及授权系统会这样做,比如 Google 的 Zanzibar 系统。 在这种情况下,服务成为与授权相关的任何数据的中心事实来源。 例如,用户在一个组织中拥有什么样的角色,或者一个仓库属于那个组织。

这种做法的好处是可以优化此服务来进行授权决策。 缺点是它现在成为了所有应用的依赖项。 而不再是我们之前提出的接口,该服务需要额外提供其他应用需要的任何数据。

最后一种方法是将相关的应用程序数据作为对授权服务请求的一部分。 首先,确定那些数据是“相关的”和授权决定一样难。 在我们之前的示例中,相关数据是用户所属的所有组织和仓库。

在需要管理一个单独的服务的复杂性这一方面,所有方法都有一些额外的缺点。

混合式

这是第三种方式,这种方式结合了上面两种方式里的一些好的方面。

在混合模式中,每个独立的应用或服务管理自己的数据及这些数据的授权。 但,应用依赖其他服务的支持来进行授权决策。

两个服务互相请求授权

在混合模式中,每个应用只管理自己的数据和相关的授权逻辑。 在必要时决策会被代理给其他应用。

当在单体应用有多个较小的辅助服务时,通常可以见到这种模型。 中央单体控制所有数据并向其他服务公开一个简单的断点。 但同样的模式也可以应用于多个服务的情况。

要完成这项工作,需要扩展我们的简单授权接口来返回更多信息。 比如,API 服务可以在请求 /org/1/repo/2 时将前用户在此仓库所拥有的权限也作为响应的一部分返回, 这样 Git 服务就可以使用这些信息做出一个简单的授权决策。

我们可以分发比这更多的授权。 也许我们有一个“组织”服务处理所有组织数据,并且它可以给我们一个用户拥有权限和组织的列表。

这种方法的强大之处在于它可以自然契合架构的形状。 也就是说,服务首先管理你设计时要管理的任何数据,授权成为其扩展。

授权数据流快速指南

总之,有几种不同的方法来做出授权决策:

  • 分布式授权是最容易实现的,应为所有应用管理自己的授权。 这是少数应用的最佳方法,或者决策依赖数据已由应用进行管理了。

  • 集中式服务有助于在跨越多个应用时保持决策逻辑的一致性,并让策略修改被解偶。 但是,这样做的缺点是你需要集中大量授权相关数据来做出决策。 当许多需要对同一组数据做出授权决策时效果很好。

  • 混合式将决策权留给单个应用,但在必要时让其他应用程序可以访问这些策略。 这是平衡应用之间逻辑和数据解偶的最佳方法,但需要以一致的方式来实现。

将所这些放在一起

实现授权需要一下组件:

  • 一个 认证系统 来鉴别是谁发起的请求(actor)。

  • 执行 ,接收请求,将其转换为(actor,action,resource)并将这些传递给授权决策过程。

  • 授权逻辑 ,它指定如何在授权数据上如何做出一个决定。

  • 决策实现 ,其将(actor,action,resource)以及授权逻辑和数据作为输入返回一个决定。

  • 返回的决策被 执行 用来返回给参与者。

虽然上面的组件有许多排列组合,但对大多数应用我们推荐一下配置:

  • 使用一个身份提供者处理认证。

  • 在应用程序中执行授权。

  • 通过添加一个授权接口将授权逻辑从应用中分离出来。

  • 保持授权数据在应用中。 这意味着当你的应用有少量服务时可以使用一个简单的中心式模型或者有许多服务时使用混合式模型来实现授权决策。

当然,每个组织都应该单独评估其用例和取舍。 如果这是您正在探索的领域,我们鼓励您加入 Oso Slack 中的开发者社区! 我们的核心工程团队也在 Slack,很乐意参与并回答您的问题。 如果您想启动在应用程序中构建授权的过程,您可以使用 Oso 并在 Oso 文档中了解有关它的更多信息。

接下来是什么

到目前为止,我们已经介绍了用于实施授权和决策的不同架构,然而,乐趣不止于此。

一旦你开始构建你的授权系统作为整个应用的一部分,其他领域就会逐渐浮现。

比如,如果我们想将授权信息传递给终端用户怎么办? 总是被告知“此操作被拒绝”绝对不是理想的用户体验。 相反,用户界面自身应该指示用户什么可以做什么不可以做。 为了做到这一点,我们需要后端返回这些信息给用户: 如果用户没有写入权限,则将修改选项置灰。

同样,我们可能想要提供一个管理员界面来显示,在当前应用中组织成员有那些权限。 这可以包括,列出应用中已存在的角色,谁对某一特定资源有特定访问权限,或者特定用户可以访问那些资源。

所有这些都涉及将授权视为整个应用的一部分。 我们将在之后的章节中回看这些用例的细节部分。

除了我们在本指南中使用的简单示例之外,仓库还有进行创建,销毁,读取或编辑等多种方式, 并可以拥有子资源(问题、维基、分支等)且其访问控制与父资源相关。 另外,权限层次结构不受限于资源。 用户可以分组到组织中,并可以进一步分组到团队和子团队中,这让控制访问的业务逻辑更加复杂。

在我们的下一章中,我们将会展示如何将常见的授权模式用于此类用例。 我们将使用 GitClub 作为示例,但我们涵盖的模式包括所有权、分层权限以及嵌套资源等对于许多面向用户的应用来说都是很常见的。