Flowable与shiro集成

299 阅读9分钟

Flowable是一个开源的工作流引擎,它提供了许多强大的功能来管理流程、任务、用户等方面的业务流程。

Shiro是一个轻量级的Java安全框架,它提供了认证、授权、加密等常见安全功能,可以方便地集成到各种Java应用中。

在Flowable中集成Shiro可以实现对业务流程的访问控制,即只允许具有特定角色或权限的用户执行某些操作。

以下是Flowable权限控制的架构图和类图的描述

                        +--------------------+
                        |   SecurityManager   |
                        +--------------------+
                                ^      ^
                                |      |
                +---------------+      +--------------+
                |                                        |
        +--------------+                          +--------------+
        |  AccessControlProvider  |          |  AuthenticationProvider |
        +--------------+                          +--------------+
                ^                                        ^
                |                                        |
        +--------------+                          +--------------+
        | AccessControlRegistry |          | AuthenticationRegistry |
        +--------------+                          +--------------+
                |                                        |
                |                                        |
        +--------------+                          +--------------+
        |   SecurityScope    |             |   AuthenticationScope   |
        +--------------+                          +--------------+

上图中展示了Flowable权限控制的类图,其中包含了以下类:

  • SecurityManager是Flowable权限控制的入口,负责协调访问控制和身份认证,它包含一个AccessControlRegistry和一个AuthenticationRegistry;
  • AccessControlRegistry用于管理所有的AccessControlProvider,可以同时存在多个AccessControlProvider,每个AccessControlProvider都提供一组访问控制规则;
  • AuthenticationRegistry用于管理所有的AuthenticationProvider,可以同时存在多个AuthenticationProvider,每个AuthenticationProvider都提供一种身份认证方式;
  • AccessControlProvider是一个接口,定义了一组访问控制规则,实现该接口的类可以提供自定义的访问控制规则;
  • AuthenticationProvider是一个接口,定义了一种身份认证方式,实现该接口的类可以提供自定义的身份认证方式;
  • SecurityScope和AuthenticationScope是用于存储上下文信息的,例如当前用户、角色、权限等,这些信息可以在整个流程执行过程中共享和使用。

Flowable权限控制的类图

          +-----------------+
          | AccessControlException |
          +-----------------+
                    ^
                    |
         +----------------------+
         | AccessControlManager |
         +----------------------+
                    ^
                    |
          +-----------------+                   +-----------------+
          | AccessControlProvider |             | AuthenticationProvider |
          +-----------------+                   +-----------------+
                    ^                                         ^
                    |                                         |
          +-----------------+                   +-----------------+
          | AccessControlRegistry |             | AuthenticationRegistry |
          +-----------------+                   +-----------------+
                    |                                         |
                    |                                         |
          +-----------------+                   +-----------------+
          |   SecurityScope   |              |  AuthenticationScope |
          +-----------------+                   +-----------------+

上图中展示了Flowable权限控制的类图,各个类的作用:

  • AccessControlException:访问控制异常;
  • AccessControlManager:访问控制管理器,负责提供所有访问控制相关的操作;AccessControlManager是权限控制的核心,它负责协调各个AccessControlRule的执行,并最终决定当前用户是否有权执行指定的操作。AccessControlManager依赖于两个核心组件:AccessControlProvider和AccessControlRegistry。
  • AccessControlProvider:访问控制规则提供者;在实现AccessControlProvider接口的类中,需要实现getRules()方法,返回一组AccessControlRule的实例,这些实例将会被AccessControlManager加载并执行。
  • AccessControlRegistry:访问控制规则注册表;用于维护AccessControlProvider实例的注册表。在实现AccessControlRegistry接口的类中,需要实现registerProvider()和unregisterProvider()方法,用于向注册表中注册或注销AccessControlProvider实例。
  • AbstractAccessControlRule:访问控制规则抽象类,所有访问控制规则必须继承自该类;根据请假流程的权限要求,判断当前用户是否有权限执行操作。
  • AuthenticationProvider:身份认证提供者;定义一组访问控制规则;
  • AuthenticationRegistry:身份认证注册表;用于向流程引擎注册访问控制规则。
  • SecurityScope:用于存储当前访问控制相关的上下文信息;
  • AuthenticationScope:用于存储当前身份认证相关的上下文信息。

集成Flowable和Shiro的一些步骤:

  1. 首先,需要在应用中引入Flowable和Shiro的相关依赖库,并配置它们的相关参数。具体的配置方式可以参考官方文档。
  2. 在Shiro中定义角色和权限,并将其映射到具体的用户上。这可以通过编写自定义的Realm类来实现。Realm类是Shiro框架中的一个核心概念,用于提供认证和授权服务。
  3. 在Flowable中配置访问控制规则。可以使用Flowable提供的AccessControlEngine类来实现。AccessControlEngine类是Flowable框架中的一个核心概念,用于提供流程和任务的访问控制服务。
  4. 将Shiro的SecurityManager注入到Flowable的AccessControlEngine中。这可以通过编写自定义的AccessControlManager类来实现。
  5. 测试集成效果。可以使用Flowable提供的JUnit测试框架来编写集成测试代码,并通过测试验证集成效果是否正确。

实现场景举例

想实现flowable 某个流程,角色权限不匹配的话就无权操作。比如请假审批流程,过程为:1.同一部门的直属领导;2.该部门总负责人; 3.公司总裁。其他部门的人员无权审批。

要实现这样的权限控制,可以通过以下步骤来完成:

  1. 定义角色和权限

在该流程中,可以定义三个角色:直属领导、部门总负责人、公司总裁,每个角色有不同的审批权限。可以在Shiro中定义这些角色,并为每个角色赋予相应的权限。

2.配置访问控制规则

可以使用Flowable提供的AccessControlEngine类来配置访问控制规则。可以通过编写自定义的AccessControlRule类来实现规则的定义。

在该流程中,可以定义以下访问控制规则:

  • 只有拥有“直属领导”角色的用户可以执行“直属领导审批”任务。
  • 只有拥有“部门总负责人”角色的用户可以执行“部门总负责人审批”任务。
  • 只有拥有“公司总裁”角色的用户可以执行“公司总裁审批”任务。

同时,需要定义一个默认规则,即任何未匹配到上述规则的用户都无法执行任务。

3.注入SecurityManager和AccessControlEngine

可以通过编写自定义的AccessControlManager类来实现SecurityManager和AccessControlEngine的注入。该类需要实现Flowable提供的AccessControlManager接口,并在其中注入Shiro的SecurityManager和自定义的AccessControlEngine。

4.编写流程定义文件

需要在流程定义文件中定义相应的任务和网关,并配置相应的访问控制规则。可以使用Flowable提供的XML或BPMN2.0格式来定义流程。

例如,在BPMN2.0文件中可以定义以下任务和网关:

  • 直属领导审批任务(包含角色为“直属领导”的用户)
  • 部门总负责人审批任务(包含角色为“部门总负责人”的用户)
  • 公司总裁审批任务(包含角色为“公司总裁”的用户)
  • 权限不匹配网关(包含所有未匹配规则的用户)

5.测试流程

可以使用Flowable提供的JUnit测试框架来编写集成测试代码,并通过测试验证权限控制效果是否正确。

例如,可以编写测试代码来测试以下情况:

  • 一个直属领导用户可以执行直属领导审批任务,但无法执行部门总负责人审批任务或公司总裁审批任务。
  • 一个部门总负责人用户可以执行直属领导审批任务和部门总负责人审批任务,但无法执行公司总裁审批任务。
  • 一个公司总裁用户可以执行直属领导审批任务、部门总负责人审批任务和公司总裁审批任务。
  • 一个非法用户无法执行任何任务。

示例代码

以下是一个基于Flowable和Shiro的权限控制示例代码。在这个例子中,假设有一个“请假审批”流程,包含三个任务:直属领导审批、部门总负责人审批和公司总裁审批。只有特定的角色可以执行任务。

  1. 定义角色和权限

可以使用Shiro的ini配置文件来定义角色和权限。例如,可以在shiro.ini文件中添加以下内容:

[roles]
leader = *
manager = leave:approve:manager
ceo = leave:approve:ceo

[urls]
/leave/approve/leader = roles[leader]
/leave/approve/manager = roles[manager]
/leave/approve/ceo = roles[ceo]

在这里,定义了三个角色:leader、manager和ceo。leader角色拥有所有权限,而manager和ceo角色只有特定的“approve”权限。在[urls]节中,配置了三个URL,每个URL都需要相应的角色才能访问。

2.配置访问控制规则

可以使用Flowable提供的AccessControlEngine类来配置访问控制规则。在这个例子中,可以创建一个自定义的AccessControlRule类来实现规则的定义。

public class LeaveAccessControlRule extends AbstractAccessControlRule {

    @Override
    public boolean isApplicable(DelegateExecution execution) {
        return execution.getProcessDefinitionId().startsWith("leaveProcess");
    }

    @Override
    public Authentication getAuthentication(DelegateExecution execution) {
        // 获取当前用户的身份认证信息
        Subject currentUser = SecurityUtils.getSubject();
        if (currentUser.isAuthenticated()) {
            return currentUser.getPrincipals();
        } else {
            return null;
        }
    }

    @Override
    public boolean hasPermission(Authentication authentication, String permission) {
        // 检查当前用户是否有权限执行指定操作
        return authentication.hasRole(permission);
    }
}


在这个规则中,使用Shiro的Subject对象来获取当前用户的身份认证信息,并检查用户是否有指定的角色权限。

3.注入SecurityManager和AccessControlEngine

可以创建一个自定义的AccessControlManager类来实现SecurityManager和AccessControlEngine的注入。

public class ShiroAccessControlManager implements AccessControlManager {

    private SecurityManager securityManager;

    public ShiroAccessControlManager(SecurityManager securityManager) {
        this.securityManager = securityManager;
    }

    @Override
    public AccessControlEngine getAccessControlEngine() {
        // 返回一个基于Shiro的AccessControlEngine
        return new ShiroAccessControlEngine(securityManager);
    }
}

在这个类中,将Shiro的SecurityManager注入到AccessControlEngine中。

4.编写流程定义文件

可以使用BPMN2.0格式来定义流程。以下是一个简单的流程定义文件示例:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:flowable="http://flowable.org/bpmn"
             xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd http://flowable.org/bpmn http://flowable.org/xsd/activity.xsd"
             targetNamespace="http://www.flowable.org/processdef">

  <process id="leaveApprovalProcess" name="请假审批流程" isExecutable="true">

    <startEvent id="startEvent" name="开始"/>

    <userTask id="leaderTask" name="直属领导审批">
      <extensionElements>
        <flowable:taskCandidateGroups>
          <flowable:candidateGroups>leader</flowable:candidateGroups>
        </flowable:taskCandidateGroups>
      </extensionElements>
    </userTask>

    <userTask id="managerTask" name="部门总负责人审批">
      <extensionElements>
        <flowable:taskCandidateGroups>
          <flowable:candidateGroups>manager</flowable:candidateGroups>
        </flowable:taskCandidateGroups>
      </extensionElements>
    </userTask>

    <userTask id="ceoTask" name="公司总裁审批">
      <extensionElements>
        <flowable:taskCandidateGroups>
          <flowable:candidateGroups>ceo</flowable:candidateGroups>
        </flowable:taskCandidateGroups>
      </extensionElements>
    </userTask>

    <endEvent id="endEvent" name="结束"/>

    <sequenceFlow id="flow1" sourceRef="startEvent" targetRef="leaderTask"/>
    <sequenceFlow id="flow2" sourceRef="leaderTask" targetRef="managerTask"/>
    <sequenceFlow id="flow3" sourceRef="managerTask" targetRef="ceoTask"/>
    <sequenceFlow id="flow4" sourceRef="ceoTask" targetRef="endEvent"/>

  </process>
</definitions>

在这个流程中,直属领导审批的任务的候选用户组是"leader",部门总负责人审批的任务的候选用户组是"manager",公司总裁审批的任务的候选用户组是"ceo"。只有拥有这些角色的用户才能完成对应的任务。

测试用例代码

测试用户拥有特定角色时,能否完成对应任务

public class ApprovalProcessTest {

    private static final String PROCESS_DEFINITION_KEY = "leaveProcess";

    private ProcessEngine processEngine;
    private RepositoryService repositoryService;
    private RuntimeService runtimeService;
    private TaskService taskService;
    private IdentityService identityService;

    private String employeeId = "employee1";
    private String directSupervisorId = "supervisor1";
    private String departmentHeadId = "departmentHead1";
    private String companyCEOId = "companyCEO1";

    @Before
    public void setUp() {
        processEngine = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration()
                .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_CREATE_DROP)
                .setJdbcUrl("jdbc:h2:mem:flowable;DB_CLOSE_DELAY=-1")
                .buildProcessEngine();

        repositoryService = processEngine.getRepositoryService();
        runtimeService = processEngine.getRuntimeService();
        taskService = processEngine.getTaskService();
        identityService = processEngine.getIdentityService();

        // 部署流程定义
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
        deploymentBuilder.addClasspathResource("leaveProcess.bpmn20.xml");
        deploymentBuilder.deploy();

        // 添加用户和组
        Group group = identityService.newGroup("directSupervisor");
        group.setName("直属领导");
        identityService.saveGroup(group);

        User user = identityService.newUser(employeeId);
        identityService.saveUser(user);
        identityService.createMembership(employeeId, "directSupervisor");

        User user2 = identityService.newUser(directSupervisorId);
        identityService.saveUser(user2);
        identityService.createMembership(directSupervisorId, "directSupervisor");

        Group group2 = identityService.newGroup("departmentHead");
        group2.setName("部门总负责人");
        identityService.saveGroup(group2);

        User user3 = identityService.newUser(departmentHeadId);
        identityService.saveUser(user3);
        identityService.createMembership(departmentHeadId, "departmentHead");

        Group group3 = identityService.newGroup("companyCEO");
        group3.setName("公司总裁");
        identityService.saveGroup(group3);

        User user4 = identityService.newUser(companyCEOId);
        identityService.saveUser(user4);
        identityService.createMembership(companyCEOId, "companyCEO");

    }

    @Test
    public void testDirectSupervisorTask() {
        // 设置当前用户为直属领导
        identityService.setAuthenticatedUserId(directSupervisorId);

        // 启动流程实例
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(PROCESS_DEFINITION_KEY);

        // 获取直属领导任务
        Task directSupervisorTask = taskService.createTaskQuery()
                .processInstanceId(processInstance.getId())
                .taskCandidateGroup("directSupervisor")
                .singleResult();

        // 直属领导完成任务
        taskService.claim(directSupervisorTask.getId(), directSupervisorId);
        taskService.complete(directSupervisorTask.getId());

        // 验证部门总负责人任务不可见
        assertNull(taskService.createTaskQuery()
                .processInstanceId(processInstance.getId())
                .taskCandidateGroup("departmentHead")
                .singleResult());

        // 验证公司总裁任务不可见
        assertNull(taskService.createTaskQuery()
                .processInstanceId(processInstance.getId())
                .taskCandidateGroup("companyCEO")
                .singleResult());
    }
}

这个测试用例首先启动了请假审批流程,并将当前用户设置为直属领导用户。然后查询当前用户的待办任务,并完成直属领导审批任务。接着验证部门总负责人和公司总裁用户都无权审批任务。

本文使用 文章同步助手 同步