RLS是Baas的基础
数据库中的创新和后端空间为开发人员构建应用程序。结果,新兴趋势是新数据库云服务提供商的兴起:
- Convex :通过 Vitess mysql。它很容易处理 large scale â - 还具有一些 speed up 的功能,并且 monitor 查询。
- Neon: postgres与存储和计算分开。专为无服务器设计,并与本机Postgres驱动程序 +支持数据库分支工作流程。
- Supabase:开源,内置的纯postgres。 >以及更多。划定付费, working on scale to zero 。
包括所有这些,我最喜欢的是supabase。原因是supabase不仅仅是托管数据库提供商,它是一个改变游戏规则的人,它提供了全面的后端作为服务(BAAS)解决方案。借助PostgreSQL数据库,身份验证,实时订阅,RESTFULE API生成和文件存储包,您甚至不需要用于Web应用程序的其他后端服务。由于一个简单的事实,即使是API生成的,通常也无法实现这一目标:
多亏了Postgres强大的行级安全性(RLS),可以通过API生成启用对数据库的安全访问。简而言之,RLS允许您定义限制基于用户属性访问行的策略。这是一个简单的示例(带有PostgreSQL):
-- source: https://www.2ndquadrant.com/en/blog/application-users-vs-row-level-security/
CREATE TABLE chat (
message_uuid UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
message_from NAME NOT NULL DEFAULT current_user,
message_to NAME NOT NULL,
message_subject VARCHAR(64) NOT NULL,
);
CREATE POLICY chat_policy ON chat
USING ((message_to = current_user) OR (message_from = current_user))
WITH CHECK (message_from = current_user)
在人类语言中,它说:
- 仅当当前用户是发件人或接收器时才可见Aâkoude -thow。
- 当插入或更新a
chat
'行时,发件人必须是当前用户。
编写细粒度访问策略后,数据库将确保只有授权用户才能基于定义的规则访问特定数据。现在,可以安全地将API公开到前端,同时确保数据隐私和安全性。
我还想要什么?
2007年,微软发布了其服务器协议,以遵守欧洲的反托拉斯调节器。但是,当他们的许多协议缺乏技术文件时,他们面临着挑战,因此团队必须根据实施来创建它们。为了确保准确性,微软创建了一个专门的团队来测试这些协议规范。为了自动化测试过程,内部工具团队开发了各种工具套件。
我以全新的毕业生加入了工具团队,他们正在开发一种新工具来从根本上解决这个问题。规格中的错误可能是因为它们与实施完全隔离。在它们之间共享一个定义将确保一致性并消除测试的需求。
这是如何引入称为“开放协议”符号(OPN)的新的DSL(特定于域的语言)。它旨在使开发人员能够建模协议体系结构,行为和数据。它可用于生成协议架构文件(IDL,WSDL等),消息解析器,仿真和技术文档。我仍然可以记得我完成了用于RPC协议的OPN的时间,该协议既可以生成公开发布的技术文档和解析/显示消息。这是我第一次听到人们会称呼它的内容:
策略与申请代码隔离
当然,这是通过数据库策略实施安全性的全部要点,但是当您想对应用程序有全面的视图时,这会很痛苦,因为很大一部分逻辑不会留在源代码中。具体的伤害可能是:
- 简单性和可维护性:它不仅使系统更难理解,而且还使调试和测试变得困难。
- 可移植性:所有数据库供应商都不始终如一。
- 版本控制:尽管数据库提供商通常具有自己的版本控制机制,但它们可以轻松地与我们的应用程序代码集成在一起。
我认为,尽管他们提供了所有好处,但您很少看到人们使用数据库存储程序的原因。
真理解决方案的单一来源
我们需要回答的第一个问题是:
如果我们想将访问策略从数据库中移动到与应用程序代码并肩相同的地方,那么在哪里?
直观地将对象关联映射(ORM)视为应用程序代码和数据库之间的桥梁,从而为代码提供了方便的抽象访问层。因此,这就是我们选择为我们正在构建的ZenStack OSS项目采取的方法。它是在Prisma ORM上方建造的,其重点之一是添加访问控制能力。这是我们之前看到的相同“聊天”场景的示例模式:
// auth() function returns the current user
// future() function returns the post-update entity value
model User {
id Int @id @default(autoincrement())
username String
sent Chat[] @relation('sent')
received Chat[] @relation('received')
// allow user to read his own profile
@@allow('read', auth() == this)
}
model Chat {
id Int @id @default(autoincrement())
subject String
fromUser User @relation('sent', fields: [fromUserId], references: [id])
fromUserId Int
toUser User @relation('received', fields: [toUserId], references: [id])
toUserId Int
// allow user to read his own chats
@@allow('read', auth() == fromUser || auth() == toUser)
// allow user to create a chat as sender
@@allow('create', auth() == fromUser)
// allow sender to update a chat, but disallow to change sender
@@allow('update', auth() == fromUser && auth() == future().fromUser)
}
当应用程序代码使用ORM与数据库交谈时,将适当的过滤器注入查询和突变以执行安全规则。例如:
- 当您进行
db.chat.findMany()
时,仅返回与当前用户相关的聊天。 - 当您使用
db.chat.create({ fromUserId: 1, toUserId: 2, subject: 'hello' })
时,如果当前用户没有ID1。 ,则ORM会拒绝您的请求
请参阅,RLS策略规则已成功地移至应用程序代码。有些人可能会问:“等一下,介绍的这个新模式文件呢?这不会打破一个真理的原理吗?
我的简短答案是架构文件也是应用程序代码的一部分。如果考虑到它,RLS功能是可以实现的,而不会损害上述简单性,可移植性和版本控制。此外,在构建过程中将模式文件转移到打字稿代码中。这只是ORM的两种不同方法之一:“ Code-First”,例如TypeoRM或“ schema-first”,例如Prisma。
虽然可以使用“代码优先”方法实现此目的,但对于开发人员来说,在没有架构的情况下表达所需的访问策略可能很困难且无直觉。 “架构优先”方法通过代码生成提供了其他好处。如果您有兴趣,可以查看我在此主题上写的另一篇文章。