行级角色
行级角色支持对特定数据行的访问限制,即,限制对实体实例的访问。
没有行级角色的用户可以访问 资源角色 允许的所有实体实例。 |
创建行级角色
可以在设计时使用带注解的 Java 接口(参考 Studio 的 行级角色创建向导)或在运行时使用 Administration(管理) → Row-level roles(行级角色) 的 UI 界面创建行级角色。
每个角色有一个用户友好的名称和一个编码。给用户分配角色时使用编码,因此如果已经有用户分配了某个角色,不要再次修改此角色的编码了。
定义设计时角色的示例:
@RowLevelRole( (1)
name = "Can see Orders with amount < 1000", (2)
code = "limited-amount-orders") (3)
public interface LimitedAmountOrdersRole {
// ...
void order(); (4)
1 | @RowLevelRole 注解表示这个接口定义了行级角色。 |
2 | 用户友好的角色名。 |
3 | 角色的编码。 |
4 | 接口可以有一个或多个方法用来定义策略注解(见下文)。不同的方法只是用来对相关的策略进行分组。方法名没有限制,当角色在 UI 展示时,方法名作为 Policy group(策略组) 展示。 |
行级策略
行级角色通过指定 行级策略 为特定实体定义访问限制。
JPQL 策略
JPQL 策略指定 where
(以及可选的 join
)子句,在加载实体时使用。
JPQL 策略通过转换 JPQL(以及 SQL)运算符,在数据库级别过滤出受限的实体实例,因此有非常好的性能。但要记住,该策略仅影响对象图根实体的加载。如果一个实体可以作为集合加载到另一个实体的对象图中,需要为其定义 JPQL 和 断言 策略。
在设计时角色中,JPQL 策略使用 @JpqlRowLevelPolicy
注解定义,示例:
@RowLevelRole(
name = "Can see only Orders created by themselves",
code = "orders-created-by-themselves")
public interface CreatedByMeOrdersRole {
@JpqlRowLevelPolicy(
entityClass = Order.class,
where = "{E}.createdBy = :current_user_username")
void order();
}
请按照下列规则编写 JPQL 策略:
-
使用
{E}
占位符代替where
和join
子句中的实体别名。框架将用查询中指定的真实别名进行替换。 -
where
中设置的文本会用and
条件添加到where
查询子句。因此无需使用where
这个单词,框架会自动添加。 -
join
中设置的文本会添加至from
查询子句中。应该以逗号、join
或left join
开头。
查询参数可以使用 会话和用户属性。例如,current_user_username
参数从用户的 username
属性中获取值。
可以为 User
实体添加项目特定的属性,并在 JPQL 策略中使用。例如,假设已将 region
属性添加到 User
和 Customer
实体。然后,仅允许用户查看其区域内的实体限制对 Customer
和 Order
的访问:
@RowLevelRole(
name = "Can see Customers and Orders of their region",
code = "same-region-rows")
public interface SameRegionRowsRole {
@JpqlRowLevelPolicy(
entityClass = Customer.class,
where = "{E}.region = :current_user_region")
void customer();
@JpqlRowLevelPolicy(
entityClass = Order.class,
where = "{E}.customer.region = :current_user_region")
void order();
}
断言策略
断言策略定义了在对实体执行不同操作时进行测试的断言。如果断言返回 true
,则允许实体实例执行该操作。
可以为这些操作定义断言策略:READ
、CREATE
、UPDATE
、DELETE
。
READ
断言在从数据库加载根实体和所有内部的实体集合(整个对象关系图)时进行测试,这一点与 JPQL 策略 不同。如果一个实体可以作为集合加载到另一个实体的对象图中,需要同时定义 JPQL 和断言策略。
CREATE
、UPDATE
、DELETE
断言在实体实例被创建、更新或从数据库中删除之前进行测试。
在设计时角色中,断言策略使用 @PredicateRowLevelPolicy
注解定义的,示例:
@RowLevelRole(
name = "Can see only non-confidential rows",
code = "nonconfidential-rows")
public interface NonConfidentialRowsRole {
@PredicateRowLevelPolicy(
entityClass = CustomerDetail.class,
actions = {RowLevelPolicyAction.READ})
default RowLevelPredicate<CustomerDetail> customerDetailNotConfidential() {
return customerDetail -> !Boolean.TRUE.equals(customerDetail.getConfidential());
}
}
此示例演示了一个行级角色,除了使用 前一节 定义的示例资源角色之外,还应使用此角色来限制对属性 confidential = true
的 CustomerDetail
实例的访问。无法使用 JPQL 策略从 Customer.details
集合中过滤掉 CustomerDetail
实例,因为在一次数据库操作中与其所属的 Customer
一并加载。而断言策略是在内存中对根实体和嵌套集合都执行测试。
在运行时角色中,断言策略是使用 Groovy 脚本定义的。在脚本中,使用 {E}
占位符作为包含测试实体实例的变量。例如,与上面设计时角色相同的条件可以写成以下 Groovy 脚本:
!{E}.confidential
访问 Spring Bean
如需在断言中访问 Spring bean,可以从方法返回 io.jmix.security.model.RowLevelBiPredicate
。这个功能接口支持定义 lambda,使用两个参数:被检查的实体实例和 Spring 的 ApplicationContext
。示例:
@RowLevelRole(
name = "Can see Customers of their region",
code = "same-region-customers-role")
public interface SameRegionCustomersRole {
@PredicateRowLevelPolicy(
entityClass = Customer.class,
actions = {RowLevelPolicyAction.READ})
default RowLevelBiPredicate<Customer, ApplicationContext> customerOfMyRegion() {
return (customer, applicationContext) -> {
CurrentAuthentication currentAuthentication = applicationContext.getBean(CurrentAuthentication.class);
return customer.getRegion() != null
&& customer.getRegion().equals(((User) currentAuthentication.getUser()).getRegion());
};
}
}
在 Groovy 脚本中,可以使用 applicationContext
变量来访问任何 Spring bean,示例:
import io.jmix.core.security.CurrentAuthentication
def authBean = applicationContext.getBean(CurrentAuthentication)
{E}.region != null && {E}.region == authBean.user.region