限制AuraDB Business CriticalAuraDB Virtual Dedicated Cloud企业版
Neo4j 的基于角色的访问控制(RBAC)存在一些限制,如果在设计安全模型或编写 Cypher 查询时未充分考虑这些限制,可能会导致意想不到的结果。根据用户所分配角色所受的限制,查询结果可能会在以下方面有所不同:
-
访问控制与索引: 索引用于加速查询,但索引返回给用户的内容取决于安全模型。如果用户对某些标签、关系类型或属性有限制,系统将不会返回任何包含这些元素的查询结果,无论使用了什么索引。此外,全文索引和向量索引返回的结果可能少于预期,因为 Lucene 机制使得 Neo4j 无法针对每个返回的索引条目检查安全规则。因此,Neo4j 仅返回能够确保不违反安全规则的结果,并排除可能违反安全规则的结果(即使实际上并未违反)。
-
失效开放(Fail-open)的
DENY行为: 当DENY(拒绝)规则的条件未满足时,它会失效开放,Neo4j 不会应用该限制。如果存在更广泛的GRANT(授权)规则,系统将默认授予访问权限。如果DENY规则设计不当,可能导致意外的数据泄露。 -
访问控制与标签: 当节点具有多个标签,且用户仅有权遍历其中部分标签时,查询结果会有所不同。这是因为用户可以看到他们有权访问的节点上附加的所有标签,即使他们并未获得遍历所有这些标签的权限。
-
访问控制与性能: 复杂的安全规则和具有限制性访问的大型图可能会导致性能显著下降,特别是对于本应高效的查询而言。
访问控制与索引
Neo4j 使用索引来加速 Cypher 查询。有关 Neo4j 中可用索引类型的详细信息,请参阅 Cypher 手册 → 索引。
然而,无论是否使用索引,Neo4j 的安全模型始终控制着您所能看到的结果。
语义索引(全文和向量)
- Lucene 的影响
-
Lucene 阻止了 Neo4j 对从索引返回的每个特定条目进行安全规则检查。因此,Neo4j 需要采取更保守的方法,以确保不返回违反安全规则的结果。如果一个结果可能违反安全规则,Neo4j 会将其视为确实违反,并将其从索引返回的结果中排除。这意味着它要么返回零结果(如果索引中的所有结果都可能受到影响),要么返回部分结果(如果可以保证某些行不受影响)。
- 使用建议
-
Cypher 不会自动使用全文索引或向量索引。您在调用全文索引的过程(如
db.index.fulltext.queryNodes和db.index.fulltext.queryRelationships)或向量索引的过程(如db.index.vector.queryNodes、db.index.vector.queryRelationships)时,或通过 Cypher 的SEARCH子句(Neo4j 2026.01 引入)时,必须显式声明要使用的索引。这避免了同一个 Cypher 查询因底层索引不同而返回不同结果的情况。问题在于,如果您不了解此行为,可能会期望全文索引或向量索引返回与语义相似的 Cypher 查询相同的结果。
以下示例说明了在使用不同安全规则时使用全文索引与搜索性能索引的影响。全文索引的示例也适用于向量索引。具有多个标签、多个关系类型或附加属性的向量索引于 Neo4j 2026.01 中引入。在以前的版本中,它们只能基于单个属性和单个标签或关系类型。
示例 1:拒绝的标签与多属性索引
该示例假设数据库包含标签为 :User 和 :Person 的节点,以及属性 name 和 surname。
-
在
name上创建一个单属性范围索引,在name和surname上创建一个复合范围索引,并在两个属性上创建一个全文索引CREATE INDEX singleProp FOR (n:User) ON (n.name); CREATE INDEX composite FOR (n:User) ON (n.name, n.surname); CREATE FULLTEXT INDEX userNames FOR (n:User|Person) ON EACH [n.name, n.surname];全文索引支持多个标签。有关创建和使用全文索引的详细信息,请参阅 Cypher 手册 → 全文搜索索引。
创建这些索引后,后两者看起来似乎达成了相同的效果。然而,这并不完全准确。
复合索引和全文索引的行为不同,侧重点也不同。一个关键区别在于,全文索引由 Lucene 支持,并使用 Lucene 语法进行查询。这对被限制了索引中涉及的标签或属性的用户有影响。理想情况下,如果索引中的标签和属性被拒绝,原生索引和全文索引都应正确地返回零结果。然而,在某些边缘情况下,情况并非如此简单。 -
让我们向数据库添加以下节点
CREATE (:User {name: 'Sandy'}); CREATE (:User {name: 'Mark', surname: 'Andy'}); CREATE (:User {name: 'Andy', surname: 'Anderson'}); CREATE (:User:Person {name: 'Mandy', surname: 'Smith'}); CREATE (:User:Person {name: 'Joe', surname: 'Andy'}); -
拒绝
:Person标签DENY TRAVERSE ON GRAPH * NODES Person TO users; -
现在运行一个使用其中一个索引的查询
运行一个使用
name上单属性范围索引的查询MATCH (n:User) WHERE n.name CONTAINS 'ndy' RETURN n.name此查询执行以下检查:
-
扫描索引以创建具有
name属性的节点流,共得出五个结果。 -
过滤结果,仅包含
n.name CONTAINS 'ndy'的节点,排除Mark和Joe,得出三个结果。 -
过滤结果,排除同时也具有被拒绝的
:Person标签的节点,过滤掉Mandy,剩余两个结果。
最后,该查询返回两个结果,但只有一个具有
surname属性。运行一个使用
name和surname复合范围索引的查询。要使用该索引,查询还需要包含surname属性的谓词MATCH (n:User) WHERE n.name CONTAINS 'ndy' AND n.surname IS NOT NULL RETURN n.name此查询执行的检查与单属性索引查询几乎相同:
-
扫描索引以创建具有
name和surname属性的节点流,共得出四个结果。 -
过滤结果,仅包含
n.name CONTAINS 'ndy'的节点,排除Mark和Joe,得出两个结果。 -
过滤结果,排除同时也具有被拒绝的
:Person标签的节点,过滤掉Mandy,剩余一个结果。
最终,它只返回一个结果。
运行与复合索引相同的查询,但改为使用全文索引
CALL db.index.fulltext.queryNodes("userNames", "ndy") YIELD node, score RETURN node.name现在的问题是,不清楚索引提供的结果是匹配了
name属性还是surname属性。查询执行以下步骤:
-
在全文索引上运行 Lucene 查询,返回在任一属性中包含
ndy的结果,得出五个结果。 -
过滤结果,排除同时也具有
:Person标签的节点,过滤掉Mandy和Joe,得出三个结果。
这种结果差异是由索引创建中两个属性之间的
OR关系引起的。 -
示例 2:拒绝所有标签上的属性
该示例使用与 示例 1 相同的数据库,但不是拒绝 :Person 标签,而是拒绝所有标签上的 surname 属性。
-
拒绝所有标签上的
surname属性DENY READ {surname} ON GRAPH * TO users; -
使用其中一个索引运行与前一个示例相同的查询
运行一个使用
name上单属性范围索引的查询MATCH (n:User) WHERE n.name CONTAINS 'ndy' RETURN n.name此查询的运行方式与之前完全相同,除了对
:Person标签的检查,因为它与被拒绝的属性无关。-
扫描索引以创建具有
name属性的节点流,共得出五个结果。 -
过滤结果,仅包含
n.name CONTAINS 'ndy'的节点,排除Mark和Joe,得出三个结果。
最终,该查询返回三个结果,其中只有一个具有
surname属性。运行一个使用
name和surname复合范围索引的查询MATCH (n:User) WHERE n.name CONTAINS 'ndy' AND n.surname IS NOT NULL RETURN n.name由于
surname属性被拒绝,它看起来始终为null,且复合索引为空。因此,查询不返回任何结果。运行与复合索引相同的查询,但改为使用全文索引
CALL db.index.fulltext.queryNodes("userNames", "ndy") YIELD node, score RETURN node.name现在的问题是,不清楚索引提供的结果是匹配了
name还是surname。来自surname属性的结果现在需要被安全规则排除,因为用户不能查看任何surname属性。然而,安全模型无法检查 Lucene 查询以确定其具体操作,无法判断它仅作用于允许的name属性还是也作用于禁止的surname属性。因此,为防止索引返回用户不应看到的结果,索引会将所有属性视为已拒绝,而不仅仅是surname属性。查询执行以下步骤:-
在全文索引上运行 Lucene 查询,返回在任一属性中包含
ndy的结果,得出五个结果。 -
过滤结果以排除所有节点,过滤掉所有人,导致没有结果。
最终,查询返回零结果,而不是预期的
Andy、Mandy和Sandy。 -
示例 3:拒绝特定标签上的属性
考虑仅对具有 :Person 标签的节点拒绝读取 surname 属性。
-
拒绝读取具有
:Person标签的节点的surname属性DENY READ {surname} ON GRAPH * NODES Person TO users; -
使用其中一个索引运行与前述示例相同的查询
运行一个使用
name上单属性范围索引的查询MATCH (n:User) WHERE n.name CONTAINS 'ndy' RETURN n.name查询的操作与 示例 2 完全相同,返回相同的三个结果,因为该查询与被拒绝的属性无关。
运行一个使用
name和surname复合范围索引的查询MATCH (n:User) WHERE n.name CONTAINS 'ndy' AND n.surname IS NOT NULL RETURN n.name由于
surname属性仅对具有:Person标签的节点被拒绝,因此它仅对这些节点表现为null,复合索引不包含这些节点。复合索引仍然包含其余具有这两个属性的:User节点。因此,查询仅返回一个结果。运行一个在
name和surname上使用全文索引的查询CALL db.index.fulltext.queryNodes("userNames", "ndy") YIELD node, score RETURN node.name现在的问题是,不清楚索引提供的结果是匹配了
name属性还是surname属性。然而,影响有所减轻,因为仅需要从surname属性的结果中排除:Person标签的节点。没有:Person标签的节点可以读取surname属性,因此无需被排除。安全模型无法检查 Lucene 查询以确定其具体操作。因此,为防止索引返回用户不应看到的结果,索引会将具有
:Person标签的节点的所有属性视为已拒绝。由于surname属性仅对具有:Person标签的节点被拒绝,索引可以安全地返回任何没有该标签的节点。查询执行以下步骤:
-
在全文索引上运行 Lucene 查询,返回在任一属性中包含
ndy的结果,得出五个结果。 -
过滤结果,排除同时也具有
:Person标签的节点,过滤掉Mandy和Joe,得出三个结果。
最终,查询返回三个结果而不是零,因为安全模型仍然允许来自没有
:Person标签的节点的结果,即使它们在被拒绝的surname属性上匹配。 -
失效开放的 DENY 行为
当 DENY 规则的条件未满足时,它会失效开放,Neo4j 不会应用该限制。如果存在更广泛的 GRANT,系统将默认授予访问权限。如果 DENY 规则设计不当,可能导致意外的数据泄露。为避免这种情况,请遵循最小权限原则,仅授予用户查看特定数据所需的权限。
例如,考虑以下场景:
基于属性的 RBAC 中未满足的 DENY 导致失效开放
您授予用户对某个属性的访问权限,并试图通过 DENY 规则进行限制。然而,如果 DENY 规则未匹配任何数据(例如,属性为 null 或拼写错误),该规则将不会生效,用户仍然可以访问该属性。
让我们看几个示例:
Employee 标签的节点上 salary 属性的读取访问权限,但对于职位为 'CEO' 的员工拒绝该权限。GRANT READ {salary} ON GRAPH * NODES Employee TO myRole;
DENY READ {salary} ON GRAPH * FOR (e:Employee) WHERE e.position = 'CEO' TO myRole;
在这种情况下,如果 e.position 属性为 null 或拼写错误,DENY 规则将不会应用,myRole 仍然会看到 salary 属性。
更好的方法是应用最小权限原则,仅对职位不是 'CEO' 的员工授予 salary 属性的访问权限。
Employee 标签且职位不是 'CEO' 的节点,授予 salary 属性的读取访问权限。GRANT READ {salary} ON GRAPH * FOR (e:Employee) WHERE e.position <> 'CEO' TO myRole;
这样,如果 e.position 为 null 或拼写错误,用户将不会看到 salary 属性,且不需要使用 DENY。
或者,如果必须使用 DENY,可以通过添加额外的 DENY 来涵盖 e.position 为 null 的情况。
salary 属性读取访问权限。DENY READ {salary} ON GRAPH * FOR (e:Employee) WHERE e.position IS NULL TO myRole;
这样,如果 e.position 为 null,用户将看不到 salary 属性。
或者,您可以添加约束以确保 e.position 属性不能为空,从而使 DENY 条件始终可检查。
position 属性不能为空。CREATE CONSTRAINT FOR (e:Employee) REQUIRE e.position IS NOT NULL;
这样,DENY 永远不会因 null 值而失效,用户将无法看到职位为 'CEO' 的员工的 salary 属性。
基于标签的 RBAC 中未满足的 DENY 导致失效开放
当 DENY 规则过于宽泛且未匹配数据时,它不会生效。
例如,如果您授予对所有节点上 salary 属性的读取访问权限,然后试图通过针对特定标签的 DENY 规则进行限制,如果图中不存在带有该标签的节点,则 DENY 规则不会应用,用户仍然可以访问所有节点上的 salary 属性。
salary 属性的读取访问权限。GRANT READ {salary} ON GRAPH * NODES * TO myRole;
这授予了对所有节点(包括不应被访问的节点)上 salary 属性的读取权限。
然后,您尝试用 DENY 规则限制对标注为 Management 的节点的 salary 属性访问。
Management 标签的节点的 salary 属性读取权限。DENY READ {salary} ON GRAPH * NODES Management TO myRole;
在这种情况下,如果 Management 标签不存在于具有 salary 属性的节点上,DENY 规则不适用,myRole 仍然可以看到该属性。
更好的方法是应用最小权限原则,仅对具有特定标签(如 IndividualContributor)的节点授予 salary 属性的访问权限。
IndividualContributor 标签的节点授予 salary 属性的读取访问权限。GRANT READ {salary} ON GRAPH * NODES IndividualContributor TO myRole;
这样,用户仅在具有 IndividualContributor 标签的节点上看到 salary 属性。
访问控制与标签
在 Neo4j 中,节点可以有多个标签,但关系只能有一种类型。在控制访问权限时,这一点很重要。
以下场景仅关注节点,因为它们可以有多个标签。同样的一般规则适用于关系,但关系比较简单。
有关访问控制权限对图遍历影响的详细信息,请参阅 图和子图访问控制。
遍历具有部分标签访问权限的多标签节点
要通过调用内置的 labels() 函数获取节点标签信息,用户需要具有使用 GRANT TRAVERSE 或 GRANT MATCH 遍历该节点上一个或多个标签的权限。对于多标签节点,即使未被授予遍历其中某些标签的权限,用户仍能看到该节点上附加的所有标签。
例如,假设一个图包含三个节点:一个标注为 :A,一个标注为 :B,一个同时标注为 :A 和 :B。
-
为
custom角色授予对:A标签节点的遍历访问权限。GRANT TRAVERSE ON GRAPH * NODES A TO custom; -
然后运行以下查询之一查看结果:
运行查询获取所有带有
:A标签节点的标签。MATCH (n:A) RETURN n, labels(n)查询返回两个节点:具有
:A标签的节点和具有:A :B标签的节点。相比之下,如果运行查询获取
:B标签节点的标签:MATCH (n:B) RETURN n, labels(n)查询仅返回具有两个标签(
:A和:B)的节点。尽管该节点在:B上没有遍历访问权限,但由于该节点同时具有被允许的:A标签,因此在数据集中可见。
遍历对某些标签被拒绝访问的多标签节点
如果用户被拒绝访问某个标签,他们将永远不会从任何带有该标签的节点获取结果。因此,该标签名称对他们来说永远不可见。
例如,假设一个图包含三个节点:一个标注为 :A,一个标注为 :B,一个同时标注为 :A 和 :B。用户有权遍历 :A 标签,但没有权遍历 :B 标签。
-
授予
custom角色对:A标签节点的遍历权限,但拒绝:B标签的遍历权限。GRANT TRAVERSE ON GRAPH * NODES A TO custom; DENY TRAVERSE ON GRAPH * NODES B TO custom; -
然后运行以下查询之一查看结果:
运行查询获取所有带有
:A标签节点的标签。MATCH (n:A) RETURN n, labels(n)查询仅返回具有
:A标签的节点。它不会返回同时具有:A和:B的节点,因为用户由于:B的存在而没有遍历该节点的权限,即使它也具有被允许的:A标签。运行查询获取
:B标签节点的标签:MATCH (n:B) RETURN n, labels(n)查询不返回任何节点。
db.labels() 过程
与前述常规图遍历不同,内置的 db.labels() 过程不处理数据图本身,而是处理定义在系统图上的安全规则。这意味着:
-
如果一个标签被显式允许(granted),它将由该过程返回。
-
如果一个标签被拒绝或未显式允许,它将不会由该过程返回。
例如,假设一个图包含三个节点:一个标注为 :A,一个标注为 :B,一个同时标注为 :A 和 :B。
-
为
custom角色授予对:A标签节点的遍历访问权限。GRANT TRAVERSE ON GRAPH * NODES A TO custom; -
运行以下查询:
CALL db.labels()查询仅返回
:A标签,因为用户没有遍历:B标签的权限。
不存在的标签、关系类型和属性名称的权限
对于不存在的标签、关系类型和属性名称的权限仅在它们创建后生效。在授权用户时,仅应用现有标签、关系类型和属性名称的权限。这是因为在用户后续使用时,图元素必须在内部解析以进行权限检查。如果标签、关系类型或属性名称尚不存在,则无法解析,因此权限不会应用。
解决方法是在创建权限时,使用 db.createLabel()、db.createRelationshipType() 和 db.createProperty() 过程在相关数据库中显式创建它们。
如果以下情况发生,则标签、关系类型或属性名称在数据库中被视为不存在:
-
从来没有节点带有该标签、关系带有该关系类型或属性带有该名称,也没有针对它们的索引或约束。
-
没有尝试添加带有该标签的节点、该关系类型的关系或该名称的属性。尝试创建操作会将名称添加到已知列表中(即使操作最终失败,除非失败原因是缺乏权限)。
-
没有尝试创建使用该标签、关系类型或属性名称的索引或约束。
-
未使用任何
db.createLabel()等过程创建它们。
目前没有办法从数据库中删除标签、关系类型或属性名称。一旦存在,它们将无法回到不存在的状态。
例如,假设您有一个名为 testing 的新空数据库。
|
本示例仅关注节点及其标签,但同样原则适用于关系类型和属性名称。 |
-
为
custom角色定义包含不存在的:A标签的权限。GRANT MATCH {*} ON GRAPH testing NODES * TO custom; GRANT CREATE ON GRAPH testing NODES A TO custom; GRANT SET LABEL A ON GRAPH testing TO custom; GRANT CREATE NEW NODE LABEL ON DATABASE testing TO custom; -
以
Alice(custom角色)身份登录并运行查询以创建带有:A标签的节点。CREATE (:A)尽管您有权创建新标签,但仍会收到异常。
Create node with labels 'A' on database 'testing' is not allowed for user 'Alice' with roles [PUBLIC, custom].
|
然而,再次运行相同的查询会成功创建节点。这是因为失败的尝试已经创建了标签,使其在第二次查询时不再属于“不存在”。 |
为了确保第一次尝试成功,管理员应在设置权限时,通过 db.createLabel() 在相关数据库中预先创建所有不存在的标签。
访问控制与性能
Neo4j 的 RBAC 可能会带来性能影响,特别是在安全规则复杂或查询大型图时。例如,count store(计数存储)操作本应是快速查找,但因为数据库必须针对安全规则检查每个节点或关系,性能可能会显著下降。
数据库操作的访问控制与性能
当存在限制对特定节点、关系或属性访问的安全规则时,Neo4j 必须执行额外检查。
例如,考虑一个具有 :Person 和 :Customer 标签的图。
-
定义两个具有不同权限的角色:
-
restricted— 遍历受限。 -
unrestricted— 遍历不受限。GRANT TRAVERSE ON GRAPH * NODES Person TO restricted; DENY TRAVERSE ON GRAPH * NODES Customer TO restricted; GRANT TRAVERSE ON GRAPH * ELEMENTS * TO unrestricted;restricted角色允许遍历:Person,但拒绝:Customer。
-
-
运行查询时,执行计划看起来相同:
统计:Person标签节点的 Cypher 查询。MATCH (n:Person) RETURN count(n)两个用户的执行计划一致。+--------------------------+ | Operator | +--------------------------+ | +ProduceResults | | | + | +NodeCountFromCountStore | +--------------------------+
然而,底层的操作可能会有显著差异:
unrestricted 用户: |
restricted 用户: |
|---|---|
数据库访问计数存储以检索 |
数据库无法利用计数存储,因为它必须确保只统计 |
属性规则的访问控制与性能
基于属性的规则需要额外的安全检查,这会产生显著的性能影响。
以下示例展示了数据库行为。
-
定义两个具有不同权限的角色:
GRANT TRAVERSE ON GRAPH * FOR (n:Customer) WHERE n.secret <> true TO restricted; GRANT TRAVERSE ON GRAPH * ELEMENTS * TO unrestricted; -
运行查询时,执行计划看起来相同:
获取所有:Customer节点的 Cypher 查询。MATCH (n:Customer) RETURN n任一角色的执行计划。+--------------------------+ | Operator | +--------------------------+ | +ProduceResults | | | + | +AllNodesScan | +--------------------------+
然而,底层的操作可能会有显著差异:
unrestricted 用户: |
restricted 用户: |
|---|---|
数据库扫描所有节点并根据标签识别,操作相对较快。 |
数据库扫描节点,识别潜在可访问节点,然后访问它们的属性值以确保满足规则(即 |