9. 数据访问控制

到目前为止,我们都是用 admin 用户登录应用程序,对 UI 和数据具有完全的访问权限。这最后一节中,我们将为 HR 经理和员工设置不同访问应用程序的权限。

员工的资源角色

创建资源角色

Jmix 工具窗口中,点击 Newadd)→ Resource Role

employee role 1

New Resource Role 弹窗中,Role name 字段输入 Employee 并在 Security scope 下拉框选择 UI

employee role 2

点击 OK

Studio 会创建并打开一个带注解的接口:

package com.company.onboarding.security;

import io.jmix.security.role.annotation.ResourceRole;

@ResourceRole(name = "Employee", code = "employee", scope = "UI")
public interface EmployeeRole {
}
UI 权限范围(scope)仅在用户通过 UI 登录系统后应用。如果同一个用户是通过 REST API 登录,则不会使用这个角色。建议为 API 创建一组不同的角色,许可范围相对少一些。

切换至 User Interface 标签页定义界面许可。在菜单树中,选中 MyOnboardingScreen,然后在右侧勾选 Allow 复选框:

employee role 3

然后切换至 Entities 标签页并选择下列权限:

employee role 4

员工需要读取 StepUserUserStep 实体以便在 UI 进行查看,还需要能更新 UserUserStep,标记步骤完成。

切回 Text 标签页。可以看到 Studio 在接口内生成了几个带注解的方法,分别对应于刚才我们设置的权限:

@ResourceRole(name = "Employee", code = "employee", scope = "UI")
public interface EmployeeRole {
    @MenuPolicy(menuIds = "MyOnboardingScreen")
    @ScreenPolicy(screenIds = "MyOnboardingScreen")
    void screens();

    @EntityAttributePolicy(entityClass = Step.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = Step.class,
            actions = EntityPolicyAction.READ)
    void step();

    @EntityAttributePolicy(entityClass = User.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = User.class,
            actions = {EntityPolicyAction.READ, EntityPolicyAction.UPDATE})
    void user();

    @EntityAttributePolicy(entityClass = UserStep.class,
            attributes = "*", action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = UserStep.class,
            actions = {EntityPolicyAction.READ, EntityPolicyAction.UPDATE})
    void userStep();
}

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。打开 AdministrationResource roles 界面,可以在列表中看到新角色:

employee role 5

分配角色

现在我们可以将角色分配给用户。打开 Users 浏览界面并创建一个新用户 bob。选择该用户并点击 Role assignments 按钮:

assign role 1

Role assignments 界面中,点击 Resource permissions 中的 Add 按钮。

弹出的 Select resource roles 对话框中,选择 EmployeeUI: minimal access 角色(使用 Ctrl/Cmd+单击):

assign role 2

点击 Select。会在 Resource permissions 面板展示选择的角色:

assign role 3

点击 OK 保存分配的角色。

用户需要 UI: minimal access 角色用来登录应用程序 UI。可以通过 Resource roles 界面打开该角色或者在 IDE 中搜索 UiMinimalRole 类查看角色内容。

使用左下角用户名右边的按钮登出系统:

assign role 4

bob 登录。则在主菜单中仅能看到 My onboarding 界面:

assign role 5

HR 经理的资源角色

Jmix 工具窗口中,点击 Newadd)→ Role

New Role 弹窗中,Role name 字段输入 HR Manager,设置 Role codehr-manager,并在 Security scope 下拉框选择 UI

manager role 1

点击 OK

Studio 会创建并打开一个带注解的接口:

package com.company.onboarding.security;

import io.jmix.security.role.annotation.ResourceRole;

@ResourceRole(name = "HR Manager", code = "hr-manager", scope = "UI")
public interface HRManagerRole {
}

切换至 User Interface 标签页并允许 User.browseUser.edit 界面(可以用顶部的搜索栏进行查找):

manager role 2

切换至 Entities 标签页,赋予对 DepartmentStep 的只读权限,UserUserStep 的所有权限:

manager role 3

切回 Text 标签页,查看 Studio 生成的带注解方法:

@ResourceRole(name = "HR Manager", code = "hr-manager", scope = "UI")
public interface HRManagerRole {
    @MenuPolicy(menuIds = "User.browse")
    @ScreenPolicy(screenIds = {"User.browse", "User.edit"})
    void screens();

    @EntityAttributePolicy(entityClass = Department.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = Department.class,
            actions = EntityPolicyAction.READ)
    void department();

    @EntityAttributePolicy(entityClass = Step.class,
            attributes = "*",
            action = EntityAttributePolicyAction.VIEW)
    @EntityPolicy(entityClass = Step.class,
            actions = EntityPolicyAction.READ)
    void step();

    @EntityAttributePolicy(entityClass = User.class,
            attributes = "*",
            action = EntityAttributePolicyAction.MODIFY)
    @EntityPolicy(entityClass = User.class,
            actions = EntityPolicyAction.ALL)
    void user();

    @EntityAttributePolicy(entityClass = UserStep.class,
            attributes = "*",
            action = EntityAttributePolicyAction.MODIFY)
    @EntityPolicy(entityClass = UserStep.class,
            actions = EntityPolicyAction.ALL)
    void userStep();
}

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。以 admin 登录。打开 AdministrationResource roles 界面,确保列表中存在新创建的 HR Manager 角色。

创建一个新用户,比如 alice

通过与 前一小节 一样的方法,给 alice 分配 HR MnagerUI: minimal access 角色。

然后以 alice 的账号登录。将可以打开 Users 界面并能管理用户和入职步骤:

manager role 4

HR 经理的行级角色

此时,HR 经理可以创建用户、为用户分配任意部门并能查看所有部门的用户。

本小节中,我们将引入一个 行级角色(row-level role),用于限制 HR 经理对部门和其他用户的访问权限。他们将只能看到并分配他们自己的部门(也就是部门中他们作为 hrManager 的那些)。

Jmix 工具窗口中,点击 Newadd)→ Row-level Role

rl role 1

New Row-level Role 弹窗中输入:

  • Role nameHR manager’s departments and users

  • Role codehr-manager-rl

  • Classcom.company.onboarding.security.HrManagerRlRole

rl role 2

点击 OK

Studio 会创建并打开一个带注解的接口:

package com.company.onboarding.security;

import io.jmix.security.role.annotation.RowLevelRole;

@RowLevelRole(
        name = "HR manager's departments and users",
        code = "hr-manager-rl")
public interface HrManagerRlRole {
}

在顶部的操作面板中点击 Add PolicyJPQL Policy

rl role 3

然后在 Add JPQL Policy 弹窗中,输入:

  • EntityDepartment

  • Where clause{E}.hrManager.id = :current_user_id

rl role 3 1

点击 OK

再次点击 Add PolicyJPQL Policy,并输入:

  • EntityUser

  • Where clause{E}.department.hrManager.id = :current_user_id

点击 OK

HrManagerRlRole 接口代码如下:

package com.company.onboarding.security;

import com.company.onboarding.entity.Department;
import com.company.onboarding.entity.User;
import io.jmix.security.role.annotation.JpqlRowLevelPolicy;
import io.jmix.security.role.annotation.RowLevelRole;

@RowLevelRole( (1)
        name = "HR manager's departments and users",
        code = "hr-manager-rl")
public interface HrManagerRlRole {

    @JpqlRowLevelPolicy( (2)
            entityClass = Department.class, (3)
            where = "{E}.hrManager.id = :current_user_id") (4)
    void department();

    @JpqlRowLevelPolicy(
            entityClass = User.class,
            where = "{E}.department.hrManager.id = :current_user_id")
    void user();
}
1 @RowLevelRole 注解表示这个接口定义的是一个行级角色。
2 @JpqlRowLevelPolicy 定义当读取实体时,在数据库层面将使用的一个策略。
3 策略应用的实体类。
4 执行实体的每个 JPQL 查询语句时,都需要添加的 where 子句。语句中使用 {E} 而非实体的别名。:current_user_id 是预定义的参数,由框架设置为当前登录用户的 id。

按下 Ctrl/Cmd+S 保存修改然后切换至运行中的程序。用 admin 登录。打开 AdministrationRow-level roles 界面,确保列表存在新添加的 HR manager’s departments and users 角色。

为用户 alice 打开 Role assignments 界面并在 Row-level constraints 表格中添加刚才创建的角色:

rl role 4

点击 OK 保存角色修改。

alice 设置为一个部门的 HR 经理:

rl role 5

alice 用户登录。

Users 浏览界面,只能看到同一部门的用户了:

rl role 6

并且 alice 只能将本部门分配给其他用户:

rl role 7

小结

本节中,我们创建了针对 HR 经理和普通员工的角色,用于对不同分组的用户限制应用程序的访问。

学习内容:

  • 一个 资源角色 控制用户对界面和特定实体访问的权限。

  • 一个 行级角色,限制用户对资源角色中许可的实体的某些实例的访问权限。

  • 运行时通过 User.browse 界面的 Role assignment 界面可以为用户分配角色。

  • 用户需要有预定义的 UI: minimal access 角色才能登录应用程序 UI。