授权

本章节中,我们介绍关于框架执行访问控制的相关内容。

数据访问检查

下表列出框架中不同机制是如何使用数据访问 权限限制

实体操作

实体属性

行级 JPQL 策略 (1)

行级 READ 谓词策略 (2)

行级 CREATE/UPDATE/DELETE 谓词策略

DataManager

(3)

UnconstrainedDataManagerEntityManager

UI 数据感知组件

- (4)

- (4)

- (4)

REST API /entities

REST API /queries

- (5)

REST API /services

- (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 加载实体并检查权限的示例来说明这个过程。

  • 在核心框架中,有一个实现了 AccessContextCrudEntityContext 类,具有以下属性:

    • entityClass - DataManager 指定正在加载的实体。授权点由该值与上下文类组成。

    • readPermitted - 此属性由访问约束提供,DataManager 根据此值决定是否继续加载实体。

  • 在安全子系统中,有一个实现 AccessConstraintCrudEntityConstraint 类,具有以下方法:

    • 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 类: