授权
本章节中,我们介绍关于框架执行访问控制的相关内容。
数据访问检查
实体操作 |
实体属性 |
行级 JPQL 策略 (1) |
行级 READ 谓词策略 (2) |
行级 CREATE/UPDATE/DELETE 谓词策略 |
|
|
是 (3) |
否 |
是 |
是 |
是 |
|
否 |
否 |
否 |
否 |
否 |
UI 数据感知组件 |
是 |
是 |
- (4) |
- (4) |
- (4) |
REST API |
是 |
是 |
是 |
是 |
是 |
REST API |
是 |
是 |
是 |
是 |
- (5) |
REST API |
是 |
是 |
- (6) |
- (6) |
- (6) |
备注:
1) 行级 JPQL 策略仅影响根实体。
// order is loaded only if it satisfies JPQL policies on the Order entity
Order order = dataManager.load(Order.class).id(orderId).one();
// related customer is loaded regardless of JPQL policies on the Customer entity
assert order.getCustomer() != null;
2) 行级谓词策略影响根实体和实体对象图中的所有关联实体。
// order is loaded only if it satisfies constraints on the Order entity
Order order = dataManager.load(Order.class).id(orderId).one();
// related customer is not null only if it satisfies predicate policies on the Customer entity
if (order.getCustomer() != null) { /*...*/ }
3) DataManager
中仅对根实体执行实体操作检查。
// loading Order
Order order = dataManager.load(Order.class).id(orderId).one();
// related customer is loaded even if the user has no permission to read the Customer entity
assert order.getCustomer() != null;
4) UI 组件本身不检查行级策略,但是当通过标准机制加载数据时,这些策略由 DataManager
执行。因此,如果实体实例被行级策略过滤掉,仍然会显示相应的 UI 组件,但内容为空。此外,对基于 SecuredListDataComponentAction
的任何操作,可以使用 setConstraintEntityOp()
方法指定某个实体操作。然后,仅当对所选实例允许操作时,才会启用该操作。
5) REST 查询都是只读的。
6) 不检查 REST 服务方法参数和结果是否符合行级策略。服务中方法行级安全的行为由如何加载和保存数据来定义,例如,是使用 DataManager
还是 UnconstrainedDataManager
。
访问约束
在本节中,我们将简要解释 Jmix 授权的原理。此信息有助于在自定义代码中 检查用户权限 或者对 Jmix 中基于角色和策略的标准权限系统进行扩展或替换。
框架机制中包含授权点,在授权点检查是否有权进行操作或访问数据。在每个授权点,都有一个 访问上下文,这是一个实现了 AccessContext
接口的类,包含关于授权主体信息的属性。
框架的任何模块、扩展组件或目标应用程序都可以为某个访问上下文(即授权点)定义和注册一组 访问约束。约束是一个实现 AccessConstraint
接口的类,带有 applyTo(AccessContext)
方法。在此方法中,实现类需要确定授权主体是否被允许,并使用此信息更新访问上下文的状态。
在授权点,需要检查授权的机制通过 AccessManager
bean 使用已有的约束。约束检查完成后,该机制此时知道上下文状态中所有约束的授权信息,然后根据这些信息决定是继续还是中止对主体的操作。
我们通过一个使用 DataManager
加载实体并检查权限的示例来说明这个过程。
-
在核心框架中,有一个实现了
AccessContext
的CrudEntityContext
类,具有以下属性:-
entityClass
-DataManager
指定正在加载的实体。授权点由该值与上下文类组成。 -
readPermitted
- 此属性由访问约束提供,DataManager
根据此值决定是否继续加载实体。
-
-
在安全子系统中,有一个实现
AccessConstraint
的CrudEntityConstraint
类,具有以下方法:-
getContextType()
返回CrudEntityContext.class
表示约束是为该上下文设计。 -
applyTo()
根据当前用户具有的 实体策略 设置CrudEntityContext.readPermitted
属性。
-
-
当
DataManager
加载实体时,会创建CrudEntityContext
的实例,设置entityClass
属性,并调用AccessManager.applyConstraints()
。之后,会分析CrudEntityContext.readPermitted
属性的值,决定继续加载实体或中止操作。
使用这种方法,将授权点与做出授权决策所需的信息完全解耦。在上面的示例中,授权点位于核心框架,而确定授权结果的代码位于安全模块(可选模块)。同样,你可以在应用程序中为 CrudEntityContext
定义一个额外的约束,以干预 DataManager
的标准授权过程。
检查用户权限
上面的章节介绍了框架代码中授权的机制。在实际应用中,很多时候我们也需要检查当前用户是否具有某种权限,此时,我们可以重用框架的相应代码。如需实现该功能,首先需要创建相应 AccessContext
的实例,然后将该实例传给 AccessManager.applyRegisteredConstraints()
方法,最后,分析 context 的状态得出结论。下面的示例展示了这一过程:
检查用户是否能读取 Customer
实体:
@Autowired
private AccessManager accessManager;
@Autowired
private Metadata metadata;
public boolean checkCustomerReadPermitted() {
MetaClass metaClass = metadata.getClass(Customer.class);
CrudEntityContext accessContext = new CrudEntityContext(metaClass);
accessManager.applyRegisteredConstraints(accessContext);
return accessContext.isReadPermitted();
}
获取用户能访问的所有 UI 视图:
@Autowired
private AccessManager accessManager;
@Autowired
private ViewRegistry viewRegistry;
public List<String> getPermittedViews() {
return viewRegistry.getViewInfos().stream()
.map(ViewInfo::getId)
.filter(screenId -> {
UiShowViewContext accessContext = new UiShowViewContext(screenId);
accessManager.applyRegisteredConstraints(accessContext);
return accessContext.isPermitted();
})
.collect(Collectors.toList());
}
参考 特定权限策略,这里有检查特定权限的示例。
下面是检查用户权限的一些通用 context 类: