我有一个多租户 Web 应用程序,可能需要支持数十个租户(公司)。我一直在寻找一种方法来确保租户只能访问他们自己的数据(重要的是没有泄漏),而不必传递tenant_id
给每个表单和 SQL 查询。我的想法是创建一个可更新的视图,以便用户的查询只能在其公司数据的范围内操作。
我通过创建一个视图(postgres)来做到这一点:
CREATE VIEW products_tenant AS
SELECT *
FROM products
WHERE company_id = cast(current_setting('my.tenant_id') as int)
with local check option;
ALTER VIEW products_tenant ALTER COLUMN company_id SET DEFAULT cast(current_setting('my.tenant_id') as int);
这将创建一个可更新的视图,该视图只允许查询该公司的数据,而无需指定其tenant_id
.
在 Diesel 中,我已经编写了表格!视图的宏,因此 Diesel 将它们视为表格。在 Rocket 中,我将我的数据库连接 Request Guard 包装在另一个 Request Guard 中,它首先发送一个 SQL 查询来设置my.tenant_id
用户的tenant_id
:
pub struct TenantView(DbConn);
...
impl<'a, 'r> FromRequest<'a, 'r> for TenantView {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, ()> {
let conn = request.guard::<DbConn>().unwrap();
let company_id = request.guard::<User>().unwrap().get_company_id();
let query = sql_query(format!("SET session my.tenant_id = {}", company_id));
query.execute(&*conn).expect("Failed to set session variable");
Outcome::Success(Self(conn))
}
}
然后,用户可以使用请求保护进行数据库查询,并且只能访问他们公司的数据。租户特定的可更新视图。
但我担心这可能会导致竞争状况。我不清楚 postgres 会话变量如何与 Diesel 和 Rocket 一起使用。假设来自两个不同公司的用户同时向 Rocket 提交请求,并且用户 A 的会话变量设置为他们的租户 ID,但在他们的交易之前,用户 B 将会话变量设置为他们的租户 ID,导致两个数据库请求都写入用户 B 的租户 ID。任何人都可以阐明这是否会成为问题?或者是否有更直接的方式来处理多租户应用程序?