如何在更少的 SQL 查询中执行复杂的 API 授权?
How to perform complex API authorization in fewer SQL queries?
我正在尝试向 API 添加授权层,而我目前的设计导致 SQL 查询比我认为应该需要的多,所以我想知道我该如何简化它。
上下文
这是这部分问题的数据库模式:
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email CITEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
name TEXT NOT NULL,
created_at DATE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS teams (
id TEXT PRIMARY KEY,
email CITEXT NOT NULL,
name TEXT NOT NULL,
created_at DATE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS memberships (
id TEXT PRIMARY KEY,
"user" TEXT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE,
team TEXT NOT NULL REFERENCES teams(id) ON UPDATE CASCADE ON DELETE CASCADE,
role TEXT NOT NULL,
created_at DATE NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE("user", team)
);
并且所讨论的 API 端点是 GET /users/:user/teams
,其中 return 是用户所属的所有团队。该路线的控制器如下所示:
(注意:所有这些都是 Javascript,但为了清楚起见,它是某种伪代码。)
async getTeams(currentId, userId) {
await exists(userId)
await canFindTeams(currentUser, userId)
let teams = await findTeams(userId)
let maskedTeams = await maskTeams(currentUser, teams)
return maskedTeams
}
这四个异步函数是获得授权所需的核心逻辑步骤"complete"。以下是每个函数的大致样子:
async exists(userId) {
let user = await query(`
SELECT id
FROM users
WHERE id = $[userId]
`)
if (!user) throw new Error('user_not_found')
return user
}
exists
只是检查 userId
的用户是否存在于数据库中,如果不存在则抛出正确的错误代码。
query
只是 运行 带有转义变量的 SQL 查询的伪代码。
async canFindTeams(currentUser, userId) {
if (currentUser.id == userId) return
let isTeammate = await query(`
SELECT role
FROM memberships
WHERE "user" = $[currentUser.id]
AND team IN (
SELECT team
FROM memberships
WHERE "user" = $[userId]
)
`)
if (!isTeammate) throw new Error('team_find_unauthorized')
}
canFindTeams
确保当前用户是发出请求的用户,或者当前用户是相关用户的队友。不应授权其他任何人查找相关用户。在我的实际实现中,它实际上是用关联了 actions
的 roles
完成的,这样队友可以 teams.read
但不能 teams.admin
除非他们是自己的。但是我在这个例子中简化了它。
async findTeams(userId) {
return await query(`
SELECT
teams.id,
teams.email,
teams.name,
teams.created_at
FROM teams
LEFT JOIN memberships ON teams.id = memberships.team
LEFT JOIN users ON users.id = memberships.user
WHERE users.id = $[userId]
ORDER BY
memberships.created_at DESC,
teams.id
`)
}
findTeams
实际上会在数据库中查询团队对象。
async maskTeams(currentUser, teams) {
let memberships = await query(`
SELECT team
FROM memberships
WHERE "user" = $[currentUser.id]
`)
let teamIds = memberships.map(membership => membership.team)
let maskedTeams = teams.filter(team => teamIds.includes(team.id))
return maskedTeams
}
maskTeams
将 return 仅给定用户应该看到的团队。这是必需的,因为用户应该能够看到他们所有的团队,但队友应该只能看到他们共同的团队,以免泄露信息。
问题
导致我像这样分解它的一个要求是我需要一种方法来抛出那些特定的错误代码,以便 returned 到 API 客户端的错误是有帮助的.例如,exists
函数在 canFindTeams
函数之前运行,因此并非所有错误都带有 403 Unauthorized
.
另一个,伪代码中没有很好地传达,是 currentUser
实际上可以是 app
(第三方客户端),team
(访问令牌属于团队本身)或 user
(常见情况)。此要求使得难以将 canFindTeams
或 maskTeams
函数实现为单个 SQL 语句,因为逻辑必须以三种方式分叉......在我的实现中,这两个函数实际上都是围绕处理所有三种情况的逻辑的 switch 语句——请求者是 app
、team
和 user
.
但即使考虑到这些限制,也感觉需要编写大量额外代码来确保所有这些身份验证要求。我担心性能、代码可维护性以及这些查询并非都在单个事务中。
问题
- 额外的查询是否会对性能产生有意义的影响?
- 能否将它们轻松组合成更少的查询?
- 有没有更好的授权设计来简化这个?
- 不使用交易会带来问题吗?
- 您还有什么要更改的吗?
谢谢!
您的 intent/requirement 向用户反映失败的详细信息并显示不同的错误是不将查询合并为更少查询的主要原因。
为了回答您明确的问题:
Do the extra queries meaningfully affect performance?
这实际上取决于带有 table 的行数。为了提高性能,您应该去测量查询的时间。这真的不能从查询(单独)来判断。通常带有 "column=VALUE" 的查询很有可能执行正常,因为 table 很小或者有适当的索引。
Can they be combined into fewer queries easily?
鉴于您显示的查询,合并是可能的。这可能会失去对身份验证失败的实际原因的区分(或为查询增加额外的复杂性)。但是,您已经说过真正的查询可能会更复杂一些。
组合几个 table 和(假设)许多替代方案(OR,UNION 需要覆盖变体)可能导致查询优化器不再找到好的计划。
因此,由于您关心性能,因此组合查询可能
对整体性能有负面影响(如往常一样进行测量)。整体性能也会受到影响,因为您的并行查询较少 运行。 (只要并行请求的数量确实很少,这就是一个好处)。
Is there a better design for the authorization that simplifies this?
根据导致此设计的几个标准,无法回答这个问题。我们需要输入关于需要实现什么以及安全策略的必须和应该是什么。在某些情况下,例如您可能会使用 PG asof 9.5 版提供的行级安全性。
Does not using transactions pose problems?
是的,一旦在执行查询时您的授权 tables 发生变化,没有事务可能会导致不一致的决策结果。例如。考虑一个用户被删除并且 canFindTeam 在 exists 查询或类似的竞争条件之前完成。
这些影响不一定是有害的,但它们确实存在。
P为了更清楚地了解这一点,请考虑对 auth tables 的可能修改(插入、删除、更新)以及对您的 auth 查询的影响(并且不要假设查询是按顺序执行的 - 你是运行 async!) 和最终决定 return 给用户。如果所有这些结果都不存在风险,那么您可能会坚持不使用交易。
否则强烈建议使用事务。
Anything else you'd change?
从安全的角度来看,提供有关故障的详细信息是一件坏事。
所以你真的应该总是 return 一个 "not authorized" 失败或者只是 return 一个空结果(并且只记录分析或调试检查的详细结果)。
我把它变成了一个函数并简化了表格,以便于测试。 SQL Fiddle。我正在做假设,因为一些规则嵌入在我不太理解的 javascript 伪代码中。
create or replace function visible_teams (
_user_id int, _current_user_id int
) returns table (
current_user_role int,
team_id int,
team_email text,
team_name text,
team_created_at date
) as $$
select
m0.role,
m0.team,
t.email,
t.name,
t.created_at
from
memberships m0
left join
memberships m1 on m0.team = m1.team and m1.user = _user_id
inner join
teams t on t.id = m0.team
where m0.user = _current_user_id
union
select null, null, null, null, null
where not exists (select 1 from users where id = _user_id)
order by role nulls first
;
$$ language sql;
Returns所有当前用户的球队加上用户共同球队:
select * from visible_teams(3, 1);
current_user_role | team_id | team_email | team_name | team_created_at
-------------------+---------+------------+-----------+-----------------
1 | 1 | email_1 | team_1 | 2016-03-13
1 | 3 | email_3 | team_3 | 2016-03-13
2 | 2 | email_2 | team_2 | 2016-03-13
(3 rows)
当用户不存在时 returns 第一个 行包含空值加上所有当前用户的团队:
select * from visible_teams(5, 1);
current_user_role | team_id | team_email | team_name | team_created_at
-------------------+---------+------------+-----------+-----------------
| | | |
1 | 1 | email_1 | team_1 | 2016-03-13
1 | 3 | email_3 | team_3 | 2016-03-13
2 | 2 | email_2 | team_2 | 2016-03-13
(4 rows)
当当前用户不存在则空集:
select * from visible_teams(1, 5);
current_user_role | team_id | team_email | team_name | team_created_at
-------------------+---------+------------+-----------+-----------------
(0 rows)
我可能(而且可能)过度简化了这一点,但让我们从简化的说明开始。您需要有关特定用户以及他们可能隶属于的任何团队的信息。通过从给定用户开始,如果它是有问题的有效用户,您将始终至少获得用户组件。只有有会员记录和对应的团队,你才会得到这个人直接关联的所有团队信息。如果此查询returns NO记录,则用户ID开头无效,您可以相应地返回0条记录。
SELECT
u.id as userid,
u.email,
u.password,
u.name,
u.created_at,
m.id as memberid,
m.team as teamid,
m.role,
m.created_at as membercreated,
t.email as teamEmail,
t.name as teamName,
t.created_at as teamCreated
from
users u
LEFT JOIN memberships m
ON u.id = m.user
LEFT JOIN teams t
ON m.team = t.id
where
u.id = UserIDYouAreInterestedIn
所以这是从用户到成员到一个人直接关联的团队,与另一个人没有关系。我没有看到这个 "other person" 参考来自哪里限制只显示普通团队的详细信息。因此,在进一步澄清之前,我将扩展这个答案并将其降低到另一个层次以获得另一个用户的所有成员资格并且他们共享同一个团队......基本上通过反转 tables 在共同成员资格/团队上的嵌套返回给用户 table。
SELECT
u.id as userid,
u.email,
u.password,
u.name,
u.created_at,
m.id as memberid,
m.team as teamid,
m.role,
m.created_at as membercreated,
t.email as teamEmail,
t.name as teamName,
t.created_at as teamCreated,
u2.name as OtherTeamMate,
u2.email as TeamMateEMail
from
users u
LEFT JOIN memberships m
ON u.id = m.user
LEFT JOIN teams t
ON m.team = t.id
LEFT JOIN memberships m2
on m.team = m2.team
AND m2.user = IDOfSomeOtherUser
LEFT JOIN users u2
on m2.user = u2.id
where
u.id = UserIDYouAreInterestedIn
我希望这是有道理的,让我澄清一下以 m2 身份重新加入会员资格。如果人 "A" 拥有团队 "X"、"Y" 和 "Z" 的会员资格,那么我想加入同一团队的会员资格 table -- 和一些其他人 ID。如果确实存在一个这样的条目,请再次转到用户 table(别名 u2)并获取队友的姓名和电子邮件。
如果有 50 个可用的团队,但人 "A" 仅适用于 3 个团队,那么它只会寻找这 3 个团队的其他可能成员和二级(m2 别名)成员的用户table是那个"other"人的ID。
在进一步思考问题并实施解决方案后,我想总结一些事情... @rpy 的回答很有帮助,请先阅读!
授权代码和数据库查询代码有一些固有的东西,可以实现更好、更面向未来的设计,让您摆脱其中两个查询。
404 不是 403
@rpy 提到的第一个问题是,出于安全目的,您不想向无权查找对象的用户显示 403 响应,因为它会泄露信息。相反,从代码中抛出的所有错误,如 403: user_find_unauthorized
都应该重新映射(无论你想让它发生什么)到 404: user_not_found
.
有了它,当 user
对象一开始就不存在时,将授权代码更改为不会失败也很容易。 (实际上,在我的例子中,我的授权代码已经以这种方式构建)。
这可以让您摆脱 exists
检查——一个查询下来。
考虑分页
第二个问题是未来的问题:当您决定稍后将分页添加到 API 时会发生什么?使用我的示例代码,分页将很难实现,因为 "querying" 和 "masking" 是分开的,因此像 LIMIT 10
这样的事情几乎不可能正确完成。
因此,虽然屏蔽代码可能会变得复杂,但您必须将其包含在原始 find
查询中,以允许分页 LIMIT
和 ORDER BY
子句。
再查询一次。
2 优于 1
毕竟,我不认为我想将最后两个查询合并为一个查询,因为它们之间的关注点分离非常有用。不仅如此,如果某人无权访问某个对象,则当前设置将很快失败,而不会因必须执行不必要的工作而对数据库负载产生负面影响。
有了所有这些,你最终会得到类似的东西:
async getTeams(currentId, userId) {
await can(['users.find', 'teams.find'], currentUser, userId)
let teams = await findTeams(currentUser, userId)
return teams
}
can
将执行授权,并且通过在 teams.find
之外提供 users.find
,它将确保未授权看起来 return 404
s。
findTeams
将执行查找,通过传递它 currentUser
它还可以包含必要的屏蔽逻辑。
希望对所有对此有疑问的人有所帮助!
我正在尝试向 API 添加授权层,而我目前的设计导致 SQL 查询比我认为应该需要的多,所以我想知道我该如何简化它。
上下文
这是这部分问题的数据库模式:
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email CITEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
name TEXT NOT NULL,
created_at DATE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS teams (
id TEXT PRIMARY KEY,
email CITEXT NOT NULL,
name TEXT NOT NULL,
created_at DATE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS memberships (
id TEXT PRIMARY KEY,
"user" TEXT NOT NULL REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE,
team TEXT NOT NULL REFERENCES teams(id) ON UPDATE CASCADE ON DELETE CASCADE,
role TEXT NOT NULL,
created_at DATE NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE("user", team)
);
并且所讨论的 API 端点是 GET /users/:user/teams
,其中 return 是用户所属的所有团队。该路线的控制器如下所示:
(注意:所有这些都是 Javascript,但为了清楚起见,它是某种伪代码。)
async getTeams(currentId, userId) {
await exists(userId)
await canFindTeams(currentUser, userId)
let teams = await findTeams(userId)
let maskedTeams = await maskTeams(currentUser, teams)
return maskedTeams
}
这四个异步函数是获得授权所需的核心逻辑步骤"complete"。以下是每个函数的大致样子:
async exists(userId) {
let user = await query(`
SELECT id
FROM users
WHERE id = $[userId]
`)
if (!user) throw new Error('user_not_found')
return user
}
exists
只是检查 userId
的用户是否存在于数据库中,如果不存在则抛出正确的错误代码。
query
只是 运行 带有转义变量的 SQL 查询的伪代码。
async canFindTeams(currentUser, userId) {
if (currentUser.id == userId) return
let isTeammate = await query(`
SELECT role
FROM memberships
WHERE "user" = $[currentUser.id]
AND team IN (
SELECT team
FROM memberships
WHERE "user" = $[userId]
)
`)
if (!isTeammate) throw new Error('team_find_unauthorized')
}
canFindTeams
确保当前用户是发出请求的用户,或者当前用户是相关用户的队友。不应授权其他任何人查找相关用户。在我的实际实现中,它实际上是用关联了 actions
的 roles
完成的,这样队友可以 teams.read
但不能 teams.admin
除非他们是自己的。但是我在这个例子中简化了它。
async findTeams(userId) {
return await query(`
SELECT
teams.id,
teams.email,
teams.name,
teams.created_at
FROM teams
LEFT JOIN memberships ON teams.id = memberships.team
LEFT JOIN users ON users.id = memberships.user
WHERE users.id = $[userId]
ORDER BY
memberships.created_at DESC,
teams.id
`)
}
findTeams
实际上会在数据库中查询团队对象。
async maskTeams(currentUser, teams) {
let memberships = await query(`
SELECT team
FROM memberships
WHERE "user" = $[currentUser.id]
`)
let teamIds = memberships.map(membership => membership.team)
let maskedTeams = teams.filter(team => teamIds.includes(team.id))
return maskedTeams
}
maskTeams
将 return 仅给定用户应该看到的团队。这是必需的,因为用户应该能够看到他们所有的团队,但队友应该只能看到他们共同的团队,以免泄露信息。
问题
导致我像这样分解它的一个要求是我需要一种方法来抛出那些特定的错误代码,以便 returned 到 API 客户端的错误是有帮助的.例如,exists
函数在 canFindTeams
函数之前运行,因此并非所有错误都带有 403 Unauthorized
.
另一个,伪代码中没有很好地传达,是 currentUser
实际上可以是 app
(第三方客户端),team
(访问令牌属于团队本身)或 user
(常见情况)。此要求使得难以将 canFindTeams
或 maskTeams
函数实现为单个 SQL 语句,因为逻辑必须以三种方式分叉......在我的实现中,这两个函数实际上都是围绕处理所有三种情况的逻辑的 switch 语句——请求者是 app
、team
和 user
.
但即使考虑到这些限制,也感觉需要编写大量额外代码来确保所有这些身份验证要求。我担心性能、代码可维护性以及这些查询并非都在单个事务中。
问题
- 额外的查询是否会对性能产生有意义的影响?
- 能否将它们轻松组合成更少的查询?
- 有没有更好的授权设计来简化这个?
- 不使用交易会带来问题吗?
- 您还有什么要更改的吗?
谢谢!
您的 intent/requirement 向用户反映失败的详细信息并显示不同的错误是不将查询合并为更少查询的主要原因。
为了回答您明确的问题:
Do the extra queries meaningfully affect performance?
这实际上取决于带有 table 的行数。为了提高性能,您应该去测量查询的时间。这真的不能从查询(单独)来判断。通常带有 "column=VALUE" 的查询很有可能执行正常,因为 table 很小或者有适当的索引。
Can they be combined into fewer queries easily?
鉴于您显示的查询,合并是可能的。这可能会失去对身份验证失败的实际原因的区分(或为查询增加额外的复杂性)。但是,您已经说过真正的查询可能会更复杂一些。 组合几个 table 和(假设)许多替代方案(OR,UNION 需要覆盖变体)可能导致查询优化器不再找到好的计划。 因此,由于您关心性能,因此组合查询可能 对整体性能有负面影响(如往常一样进行测量)。整体性能也会受到影响,因为您的并行查询较少 运行。 (只要并行请求的数量确实很少,这就是一个好处)。
Is there a better design for the authorization that simplifies this?
根据导致此设计的几个标准,无法回答这个问题。我们需要输入关于需要实现什么以及安全策略的必须和应该是什么。在某些情况下,例如您可能会使用 PG asof 9.5 版提供的行级安全性。
Does not using transactions pose problems?
是的,一旦在执行查询时您的授权 tables 发生变化,没有事务可能会导致不一致的决策结果。例如。考虑一个用户被删除并且 canFindTeam 在 exists 查询或类似的竞争条件之前完成。
这些影响不一定是有害的,但它们确实存在。 P为了更清楚地了解这一点,请考虑对 auth tables 的可能修改(插入、删除、更新)以及对您的 auth 查询的影响(并且不要假设查询是按顺序执行的 - 你是运行 async!) 和最终决定 return 给用户。如果所有这些结果都不存在风险,那么您可能会坚持不使用交易。 否则强烈建议使用事务。
Anything else you'd change?
从安全的角度来看,提供有关故障的详细信息是一件坏事。 所以你真的应该总是 return 一个 "not authorized" 失败或者只是 return 一个空结果(并且只记录分析或调试检查的详细结果)。
我把它变成了一个函数并简化了表格,以便于测试。 SQL Fiddle。我正在做假设,因为一些规则嵌入在我不太理解的 javascript 伪代码中。
create or replace function visible_teams (
_user_id int, _current_user_id int
) returns table (
current_user_role int,
team_id int,
team_email text,
team_name text,
team_created_at date
) as $$
select
m0.role,
m0.team,
t.email,
t.name,
t.created_at
from
memberships m0
left join
memberships m1 on m0.team = m1.team and m1.user = _user_id
inner join
teams t on t.id = m0.team
where m0.user = _current_user_id
union
select null, null, null, null, null
where not exists (select 1 from users where id = _user_id)
order by role nulls first
;
$$ language sql;
Returns所有当前用户的球队加上用户共同球队:
select * from visible_teams(3, 1);
current_user_role | team_id | team_email | team_name | team_created_at
-------------------+---------+------------+-----------+-----------------
1 | 1 | email_1 | team_1 | 2016-03-13
1 | 3 | email_3 | team_3 | 2016-03-13
2 | 2 | email_2 | team_2 | 2016-03-13
(3 rows)
当用户不存在时 returns 第一个 行包含空值加上所有当前用户的团队:
select * from visible_teams(5, 1);
current_user_role | team_id | team_email | team_name | team_created_at
-------------------+---------+------------+-----------+-----------------
| | | |
1 | 1 | email_1 | team_1 | 2016-03-13
1 | 3 | email_3 | team_3 | 2016-03-13
2 | 2 | email_2 | team_2 | 2016-03-13
(4 rows)
当当前用户不存在则空集:
select * from visible_teams(1, 5);
current_user_role | team_id | team_email | team_name | team_created_at
-------------------+---------+------------+-----------+-----------------
(0 rows)
我可能(而且可能)过度简化了这一点,但让我们从简化的说明开始。您需要有关特定用户以及他们可能隶属于的任何团队的信息。通过从给定用户开始,如果它是有问题的有效用户,您将始终至少获得用户组件。只有有会员记录和对应的团队,你才会得到这个人直接关联的所有团队信息。如果此查询returns NO记录,则用户ID开头无效,您可以相应地返回0条记录。
SELECT
u.id as userid,
u.email,
u.password,
u.name,
u.created_at,
m.id as memberid,
m.team as teamid,
m.role,
m.created_at as membercreated,
t.email as teamEmail,
t.name as teamName,
t.created_at as teamCreated
from
users u
LEFT JOIN memberships m
ON u.id = m.user
LEFT JOIN teams t
ON m.team = t.id
where
u.id = UserIDYouAreInterestedIn
所以这是从用户到成员到一个人直接关联的团队,与另一个人没有关系。我没有看到这个 "other person" 参考来自哪里限制只显示普通团队的详细信息。因此,在进一步澄清之前,我将扩展这个答案并将其降低到另一个层次以获得另一个用户的所有成员资格并且他们共享同一个团队......基本上通过反转 tables 在共同成员资格/团队上的嵌套返回给用户 table。
SELECT
u.id as userid,
u.email,
u.password,
u.name,
u.created_at,
m.id as memberid,
m.team as teamid,
m.role,
m.created_at as membercreated,
t.email as teamEmail,
t.name as teamName,
t.created_at as teamCreated,
u2.name as OtherTeamMate,
u2.email as TeamMateEMail
from
users u
LEFT JOIN memberships m
ON u.id = m.user
LEFT JOIN teams t
ON m.team = t.id
LEFT JOIN memberships m2
on m.team = m2.team
AND m2.user = IDOfSomeOtherUser
LEFT JOIN users u2
on m2.user = u2.id
where
u.id = UserIDYouAreInterestedIn
我希望这是有道理的,让我澄清一下以 m2 身份重新加入会员资格。如果人 "A" 拥有团队 "X"、"Y" 和 "Z" 的会员资格,那么我想加入同一团队的会员资格 table -- 和一些其他人 ID。如果确实存在一个这样的条目,请再次转到用户 table(别名 u2)并获取队友的姓名和电子邮件。
如果有 50 个可用的团队,但人 "A" 仅适用于 3 个团队,那么它只会寻找这 3 个团队的其他可能成员和二级(m2 别名)成员的用户table是那个"other"人的ID。
在进一步思考问题并实施解决方案后,我想总结一些事情... @rpy 的回答很有帮助,请先阅读!
授权代码和数据库查询代码有一些固有的东西,可以实现更好、更面向未来的设计,让您摆脱其中两个查询。
404 不是 403
@rpy 提到的第一个问题是,出于安全目的,您不想向无权查找对象的用户显示 403 响应,因为它会泄露信息。相反,从代码中抛出的所有错误,如 403: user_find_unauthorized
都应该重新映射(无论你想让它发生什么)到 404: user_not_found
.
有了它,当 user
对象一开始就不存在时,将授权代码更改为不会失败也很容易。 (实际上,在我的例子中,我的授权代码已经以这种方式构建)。
这可以让您摆脱 exists
检查——一个查询下来。
考虑分页
第二个问题是未来的问题:当您决定稍后将分页添加到 API 时会发生什么?使用我的示例代码,分页将很难实现,因为 "querying" 和 "masking" 是分开的,因此像 LIMIT 10
这样的事情几乎不可能正确完成。
因此,虽然屏蔽代码可能会变得复杂,但您必须将其包含在原始 find
查询中,以允许分页 LIMIT
和 ORDER BY
子句。
再查询一次。
2 优于 1
毕竟,我不认为我想将最后两个查询合并为一个查询,因为它们之间的关注点分离非常有用。不仅如此,如果某人无权访问某个对象,则当前设置将很快失败,而不会因必须执行不必要的工作而对数据库负载产生负面影响。
有了所有这些,你最终会得到类似的东西:
async getTeams(currentId, userId) {
await can(['users.find', 'teams.find'], currentUser, userId)
let teams = await findTeams(currentUser, userId)
return teams
}
can
将执行授权,并且通过在 teams.find
之外提供 users.find
,它将确保未授权看起来 return 404
s。
findTeams
将执行查找,通过传递它 currentUser
它还可以包含必要的屏蔽逻辑。
希望对所有对此有疑问的人有所帮助!