Web 应用程序中的身份验证和访问控制指南🔐
在 Wasp,我们正在开发一种用于构建与 React 和 Node.js 集成的 Web 应用的配置语言 / DSL。
这要求我们深入了解 Web 应用的各个组成部分,以便能够在我们的 DSL 中对其进行建模。
最近我们的重点是访问控制,我决定在这篇博文中记录所学知识,以帮助其他人快速了解如何在 Web 应用程序中进行访问控制。
因此,如果您不熟悉 Web 应用程序的访问控制,或者已经使用了一段时间,但想要更好地了解标准做法,请继续阅读!
这篇博文内容的简要概述:
- 权限,耶!等等,它们是什么?(基本术语概述)
- 我们应该在哪里检查 Web 应用中的权限:前端、后端还是数据库
- 常见方法(RBAC、ABAC 等)
- OWASP 建议
- 在实践中实施访问控制
- 摘要(TLDR)
1. 权限,耶!等等,它们是什么?
除非您的网络应用程序主要涉及静态内容或是一种艺术形式,否则它可能会具有用户和用户帐户的概念。
在这种情况下,您需要知道哪个用户有权限做什么->谁可以访问哪些资源,谁可以执行哪些操作。
权限操作的一些常见示例:
- 用户只能访问自己的用户帐户。
- 如果用户是管理员,他们可以禁止其他用户的帐户。
- 用户可以阅读其他用户的文章,但不能修改。
- 付费墙后面的文章的标题和描述是公开的,但内容却不能公开。
- 用户每天最多可以向 10 位未来用户发送电子邮件邀请。
啊哈,你是说访问控制!抱歉,是授权!嗯,是身份验证?
有许多不同的术语(身份验证、授权、访问控制、权限)经常被混淆,所以让我们快速澄清一下它们各自代表什么。
1)身份验证(或者更酷的说法:authN)
验证用户身份的行为。回答“他们是谁? ”
的问题。
A:敲门
B:是谁?
A:用户!
B:用户是谁?
A:授权:基本我的用户名:我的密码-> 是的,你没看错,这是一个常见的身份验证方法的例子,但 HTTP 标头却被称为“Authorization”!奇怪!不过,如果你仔细想想,就明白了:( https://stackoverflow.com/questions/30062024/why-is-the-http-header-for-authentication-called-authorization )。
2)授权(或者像酷孩子会说的那样:authZ)
确定用户访问权限的过程。回答“他们可以这样做吗? ”
这个问题。
通常,您希望此时用户已经通过身份验证,因此您可以获得有关他们的信息,并根据这些信息决定是否允许他们执行某些操作。
3)访问控制
更高级别的术语(与 authN 和 authZ 相比),涵盖确保只有允许的方才能访问特定资源的整个过程(控制对资源的访问 -> 访问控制)。
通常包括身份验证和/或授权作为其步骤。
在实际应用中,它经常与“授权”互换使用。
参考 (OWASP):https://www.cgisecurity.com/owasp/html/ch08.html
4) 许可
一个更通用/非正式的术语,在计算机科学的背景下使用时,其含义最接近“授权”。
访问资源的许可称为授权。
全部在一起
让我们通过观察以下想象的拉取请求(PR)来看一下这些术语在句子中的用法:
标题:为应用程序添加了访问控制。
描述:我实现了用户通过电子邮件和密码或通过 Google 进行身份验证的
方法。 在服务器端,我为大多数 REST API 处理程序添加了权限检查,以确保经过身份验证的用户有权执行这些处理程序。 如果用户未获得授权,我们会抛出 HTTP 错误 403。 此外,REST API 的一些公共部分无需用户进行身份验证。
觉得这篇文章有用吗?
我们正在Wasp上努力创建这样的内容,更不用说构建一个现代的开源 React/NodeJS 框架,让您只需几行代码即可“自行完成”身份验证。
如果您想看到更多类似的内容,您可以在 GitHub 上给我们一颗星,从而轻松地帮助我们! 。
2. 在 Web 应用中,我们应该在哪里检查权限:前端、后端还是数据库
我们解释了很多术语,现在让我们看看访问权限检查在实践中是如何进行的!
在典型的 Web 应用程序中,您将拥有前端、后端(服务器)和数据库。
前端将向服务器发出命令,服务器随后执行操作,并可能(代表用户)修改数据库。由于用户无法直接访问数据库,并且前端本身并不安全,因此服务器成为了所有关键访问控制需要进行的中心位置。
前端(浏览器)
前端是指 Web 客户端 -> 在浏览器中执行的代码(例如 JavaScript)。
前端用于帮助用户向服务器发出命令,通过该命令用户可以访问和/或修改我们的 Web 应用程序的资源(通常存储在数据库中)。
由于用户可以随意操作前端代码,我们实际上无法在前端代码中进行任何权限检查,我们不能信任它!
任何我们在前端进行的权限检查,无论如何都需要在服务器上重复进行。
如果是这样,我们是否应该在前端检查权限?这样做的目的是什么?
在前端进行权限检查的主要原因是人体工程学/用户体验-> 通过让 UI 只关注他们可以更改的资源,我们让用户更容易理解他们可以在我们的 Web 应用中做什么,并确保他们不会浪费时间去描述服务器无法执行的复杂操作。
因此,例如,如果用户无法访问 UI 表单中的某些字段,我们的前端代码可以隐藏/省略它们;它可以阻止打开某些页面;或者如果某些按钮触发用户无权执行的操作,则隐藏/省略它们。
要点:前端的权限检查不是为了安全,而只是为了人体工程学/改善用户体验。
后端(服务器)
服务器是实现访问控制的关键所在。它对外暴露 API,供前端(浏览器)和/或其他服务使用。在执行这些操作时,这些服务会向服务器进行身份验证,以便服务器知道它们的身份,然后它们会请求(例如通过 REST API 或 GraphQL API)服务器执行某些操作(例如创建、更新或获取数据)。服务器的工作是确定它们是否被允许(授权)执行这些操作(针对指定的资源/使用提供的参数),如果不被允许,则拒绝它们。
服务器上的权限检查本质上是为了检查每个 API 端点是否允许调用者执行该端点。它们通常在 API 端点逻辑的最开始执行,但通常也与端点处理程序的其余逻辑交织在一起。
除了在 API/操作级别定义检查之外,它们通常也在数据/模型级别定义。这意味着它们与特定的数据模型(通常来自数据库)绑定,作为数据访问逻辑(ORM)的一部分,并定义谁可以访问特定字段,甚至整个数据模型。
在 GraphQL 模式中直接将权限检查附加到数据模型的示例(来自此博客文章):
要了解更复杂的 RBAC 方法以及额外的间接层(权限),请继续阅读。
数据库
通常,用户根本无法直接访问数据库,而是通过服务器进行操作。在这种情况下,除了数据库中为确保数据模型完整性(例如唯一性、验证等)而设置的常规约束之外,无需进行特定的数据库访问控制。话虽如此,在某些情况下,您可能需要这样做,但本文不会对此进行讨论。
3.常见方法(RBAC、ABAC……)
最常见的访问控制方法是RBAC(基于角色的访问控制)和ABAC(基于属性的访问控制),其中 RBAC 占据主导地位(但 ABAC 正在兴起)。
尽管不太受欢迎,但我们也会简要提及 ReBAC 作为“中间”选项。
RBAC——基于角色的访问控制
角色规则 :D!在RBAC中,角色是核心概念。一些示例角色可能是admin
、、、 …… guest
。在确定某个用户是否具有访问权限时,writer
我们会检查其角色并据此确定其访问权限。例如,可以删除其他用户、文章和项目,但不能修改任何资源,只能阅读文章。moderator
admin
guest
专业建议(感谢 Karan!):虽然我们可以在权限检查中直接检查用户的角色,但更好的做法(OWASP 也推荐)是添加一个间接层→权限。这样,角色就附加到用户,权限又附加到角色,权限检查再检查权限(谁会想到呢 :)!?)。
例如,一个用户可能拥有 角色admin
,并且 角色admin
具有附加的权限updateArticle
和deleteArticle
。然后,在确定用户是否可以删除文章时,我们首先获取他的角色,然后获取附加到该角色的权限,最后检查 是否deleteArticle
存在于其中 → 如果存在,则可以继续删除!
这样,如果我们决定某个角色应该拥有更多或更少的权限,我们只需向该角色添加或删除相应的权限即可!我们不必进行每次权限检查并更新其逻辑(如果我们直接针对角色进行检查,就必须这样做)。
RBAC 之所以流行,是因为它相对简单,并且能够很好地反映基本的业务领域——在现实世界中,我们经常以角色的方式思考,因此它很容易掌握和理解。目前有很多解决方案和框架实现了 RBAC。
虽然 RBAC 非常适合许多常见用例,但它也有一个缺点——当访问控制变得复杂时(这通常发生在 Web 应用程序发展和变大时),RBAC 有时无法以优雅的方式提供所需的粒度,从而导致笨重且过于复杂的访问控制逻辑。
ABAC——基于属性的访问控制
在ABAC中,关键思想是定义一系列访问控制规则,每个规则以不同的“属性”作为输入。当需要检查用户是否有权执行某项操作时,运行所有规则,如果所有规则都通过,则操作成功;如果一条规则失败,则操作失败。
规则属性可以是任何内容,但通常分为 4 类:
- 主题:有关用户的信息(即用户的 ID 或姓名)
- 动作:他们想要执行的操作(即阅读文章)
- 对象:他们想要操作的资源(即文章),
- 环境/上下文:即一天中的当前时间或用户在过去一小时内发出的请求数。
让我们观察之前的例子,我们想知道用户是否被允许删除一篇文章。
在 ABAC 中,我们可以定义一个动作“deleteArticle”,然后定义一条规则,该规则包含用户(主体)、动作、对象和其他上下文。该规则会检查动作是否为“deleteArticle”,如果是,则会通过检查用户的某些属性(甚至是角色),或者检查用户是否是该文章的所有者,来评估用户是否有权删除指定为对象的文章。
然后,当用户实际发出删除文章的命令时,我们会要求我们的访问控制系统根据其拥有的所有规则运行该命令,同时为其提供(用户,“deleteArticle”,文章,上下文)元组→大多数规则会说一切正常,因为它们与“deleteArticle”操作无关,但那些(比如我们上面定义的)必须全部通过才能真正允许访问。
ABAC 作为一种方法非常灵活且通用,您可以轻松地在 ABAC 中实现 RBAC(和许多其他方法)(通过检查用户的角色作为属性之一)→因此它比 RBAC 更通用/更具表现力。
然而,ABAC 的实现更加复杂,并且性能成本也更高,因为每次执行访问控制检查时都需要检查多条规则。
ReBAC - 基于关系的访问控制
当您需要根据与关系相关的问题(例如“该用户是否是该文章的所有者”或“该用户是否属于该工作区”)授予访问权限时,角色(RBAC)可能会有所欠缺。
虽然 ABAC 可以轻松处理这个问题,但如果您需要描述的只是关系,那么您可能会认为它有点过于强大 → 而这正是 ReBAC 的用武之地。
虽然实现 ReBAC 的方法有很多,但最简单的方法是在 RBAC 的基础上构建,在访问控制逻辑中引入“关系”规则的概念,然后与角色一起检查这些规则。因此,RBAC 带有一点 ABAC(专注于关系)的特色。
4. OWASP建议
当在线寻找有关如何在 Web 应用程序中进行访问控制的“官方”/标准化建议时,您很可能会找到由 OWASP 制作的资源。
OWASP 的定义:开放 Web 应用程序安全项目® (OWASP) 是一个致力于提高软件安全性的非营利基金会。
我发现他们有不少关于如何在 Web 应用程序中进行访问控制的资源,其中最有趣的是以下内容:
我从他们的材料中提取了几个对我来说最有意义的要点:
- 集中访问控制逻辑,以便于审查。
- 默认拒绝访问。
- 相比 RBAC,更喜欢 ABAC。
5. 在实践中实施访问控制
这是我在 r/webdev 上进行的 Reddit 民意调查。
一个有趣的发现是,尽管样本很小,但很明显开发人员更喜欢 RBAC 而不是 OWASP 推荐的 ABAC。
我认为这主要有两个原因:RBAC 更简单 + 支持 RBAC 的库/框架比支持 ABAC 的库/框架更多(同样,因为它更简单)。
不过,ABAC 最近确实似乎有所回升,因此,将来重复进行这项民意调查并看看有何变化将会很有趣。
有机发展
我们通常会根据需要,逐一为 Web 应用添加权限检查。例如,如果我们使用 NodeJS 和 ExpressJS 作为服务器,并编写处理 HTTP API 请求的中间件,我们会在该中间件中添加一些逻辑,进行一些检查,以确保用户确实可以执行该操作。或者,我们可能会将“检查”嵌入到数据库查询中,以便只查询用户有权访问的内容。通常是这两种方法的组合。
这种有机方法的危险之处在于,随着代码库的增长,复杂性会增加——如果我们没有投入足够的精力来集中和构建我们的访问控制逻辑,那么对其进行推理和持续更新就会变得非常困难,从而导致错误和漏洞。
想象一下,现在用户只能阅读自己和好友的文章,而之前他们可以阅读任何文章,那会是怎样的体验?如果只有一个地方可以进行更新,我们还能安心地等待。但如果有很多地方可以进行更新,我们需要先找到所有地方,然后确保所有地方都以相同的方式进行更新,那么我们就会面临很多麻烦,也更容易犯错。
使用现有解决方案
与其自己琢磨如何构建访问控制代码,不如使用现有的访问控制解决方案!除了无需自己思考和实现所有功能外,另一个巨大的优势是这些解决方案都经过了实践检验,这对于处理 Web 应用安全的代码至关重要。
我们可以粗略地将这些解决方案分为框架和(外部)提供商,其中框架嵌入到您的 Web 应用程序中并与其一起交付,而提供商是外部托管的,通常是付费服务。
一些流行的解决方案:
- https://casbin.org/(多种方法、多种语言、提供商)
- 开源 authZ 库,支持多种访问控制模型(ACL、RBAC、ABAC 等)和多种语言(Go、Java、Node.js、JS、Rust 等)。虽然略显复杂,但功能强大且灵活。他们还有自己的 Casdoor 平台,提供 authN 和 authZ 服务。
- https://casl.js.org/v5/en/(ABAC,Javascript)
- 用于 ABAC 的开源 JS/TS 库。CASL 为您提供了一种在 Web/NodeJS 代码中定义 ABAC 规则、检查并调用这些规则的好方法。它与 React、Angular、Prisma、Mongoose 等众多流行解决方案集成。
- https://github.com/CanCanCommunity/cancancan(Ruby on Rails ABAC)
- 和 casl.js 类似,但适用于 Ruby on Rails!Casl.js 实际上是受到了 cancancan 的启发和模仿。
- https://github.com/varvet/pundit
- 流行的开源 Ruby 库专注于策略概念,让您可以自由地在此基础上实施自己的方法。
- https://spring.io/projects/spring-security
- 适用于 Spring(Java)的开源 authN 和 authZ 框架。
- https://github.com/dfunckt/django-rules a. 一个通用的、易于理解的开源框架,用于在 Django(Python)中构建基于规则的系统。
- Auth0(提供商)
- Auth0 已经存在一段时间了,可能是目前最受欢迎的身份验证提供商。虽然身份验证是他们的主要产品(他们提供身份验证 SDK,并存储用户配置文件,并允许用户通过其 SaaS 进行管理),但他们也允许你通过 RBAC 和策略在一定程度上定义身份验证。
- https://www.osohq.com/(提供商,DSL)
- OSO 是一家授权服务提供商,其独特之处在于他们拥有一种专门用于授权的语言(DSL,名为 Polar),您可以使用它来定义授权规则。他们不仅支持常用方法(例如 RBAC、ABAC、ReBAC),还支持自定义方法。因此,您可以在应用程序中嵌入他们的开源库,或者使用他们的托管云产品。
- https://warrant.dev/(提供商)
- 作为一家相对较新的 authZ 提供商,他们提供了一个仪表盘,您可以在其中集中管理规则,然后通过 SDK 以多种语言使用这些规则,甚至可以在客户端执行 UI 检查。规则也可以通过 SDK 以编程方式进行管理。
- https://authzed.com/(提供商)
- AuthZed 提供了一个专门的 SpiceDB 权限数据库,用于集中存储和管理规则。然后,您可以使用他们的 SDK 来查询、存储和验证应用程序权限。
- https://clerk.com/(提供商)
- Clerk 是一款用户管理解决方案,提供预构建的 React/JS 组件,以及用于多种登录选项的 API 和 SDK。它还高度重视最新的安全最佳实践。
摘要(TLDR)
- 身份验证(authN)回答“他们是谁”,授权(authZ)回答“他们被允许吗”,而访问控制是执行 authN 和 authZ 的整个过程的总称。
- 在前端进行访问控制只是为了做做样子(为了提升用户体验),你不能依赖它。所有真正的访问控制都需要在服务器上完成(可能在数据库中也有一些,但通常不需要)。
- 虽然一开始可以从简单的访问控制方法入手,但一旦复杂性增加,您应该准备好切换到更高级的方法。最流行的访问控制方法是RBAC(基于角色)和ABAC(基于属性)。RBAC 更容易上手,但 ABAC 更强大。
- 您应该确保您的访问控制尽可能少重复并且是集中的,以减少引入错误的机会。
- 使用现有的解决方案(如访问控制框架或外部提供商)通常是明智的。
Wasp 中的访问控制
在Wasp中,我们允许您仅用几行代码即可“自行”实现身份验证。这意味着代码完全属于您,您无需依赖昂贵的第三方提供商。
我们还没有对访问控制的特殊支持,但我们很快就会添加它!
同时,如果您喜欢这篇文章并想表达支持,您可以在 GitHub 上给我们一个星星!
在不久的将来,我们可能会为 Wasp 引入 ABAC,并希望能够提供一种在操作级别和实体(数据模型)级别定义访问规则的方法。Wasp 的使命是提供高度集成的全栈体验,因此,我们很高兴看到这项技术能够提供与整个 Web 应用紧密集成、贯穿整个堆栈的访问控制解决方案!
您可以在我们的“权限支持”RFC中查看有关此问题的讨论。
感谢审稿人
Karan Kajla(RBAC 方面的专业建议!)、Graham Neray(很棒的通用建议以及 ReBAC 的详尽说明)、Dennis Walsh(关于如何提升文章可读性的绝妙建议)、Shayne Czyzewski和Matija Sosic,感谢你们抽出时间审阅本文并加以改进!你们的建议、指正和想法非常宝贵。
文章来源:https://dev.to/wasp/a-guide-to-auth-access-control-in-web-apps-2410