Contents

Activiti 与 Spring Security 集成

1. 概述

Activiti 是一个开源 BPM(业务流程管理)系统。有关介绍,请查看我们的 Java Activiti 指南

Activiti 和 Spring 框架都提供了自己的身份管理。但是,在集成了这两个项目的应用程序中,我们可能希望将两者组合成一个用户管理流程。

在下文中,我们将探索实现这一目标的两种可能性:一种是通过为 Spring Security 提供 Activiti 支持的用户服务,另一种是通过将 Spring Security 用户源插入到 Activiti 身份管理中。

2. Maven依赖

要在 Spring Boot 项目中设置 Activiti,请查看我们之前的文章 。除了activiti-spring-boot-starter-basic,我们还需要activiti-spring-boot-starter-security 依赖:

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter-security</artifactId>
    <version>6.0.0</version>
</dependency>

3. 使用 Activiti 进行身份管理

对于这个场景,Activiti 启动器提供了一个 Spring Boot 自动配置类,它使用HTTP Basic身份验证保护所有 REST 端点。

自动配置还创建了IdentityServiceUserDetailsService类的UserDetailsService bean

该类实现了 Spring 接口UserDetailsService并覆盖了loadUserByUsername()方法。此方法检索具有给定id的 Activiti User对象,并使用它来创建 Spring UserDetails对象。

此外,Activiti Group对象对应于一个 Spring 用户角色。

这意味着当我们登录 Spring Security 应用程序时,我们将使用 Activiti 凭据。

3.1. 设置 Activiti 用户

首先,让我们使用IdentityService在主*@SpringBootApplication类中定义的InitializingBean*中创建一个用户:

@Bean
InitializingBean usersAndGroupsInitializer(IdentityService identityService) {
    return new InitializingBean() {
        public void afterPropertiesSet() throws Exception {
            User user = identityService.newUser("activiti_user");
            user.setPassword("pass");
            identityService.saveUser(user);
            Group group = identityService.newGroup("user");
            group.setName("ROLE_USER");
            group.setType("USER");
            identityService.saveGroup(group);
            identityService.createMembership(user.getId(), group.getId());
        }
    };
}

您会注意到,**由于 Spring Security 将使用它,因此Group对象*name 必须采用“ROLE_X”*形式。

3.2. Spring 安全配置

如果我们想使用不同的安全配置而不是 HTTP Basic 身份验证,首先我们必须排除自动配置:

@SpringBootApplication(
  exclude = org.activiti.spring.boot.SecurityAutoConfiguration.class)
public class ActivitiSpringSecurityApplication {
    // ...
}

然后,我们可以提供我们自己的 Spring Security 配置类,它使用IdentityServiceUserDetailsService 从 Activiti 数据源中检索用户:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private IdentityService identityService;
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth)
      throws Exception {
 
        auth.userDetailsService(userDetailsService());
    }
    
    @Bean
    public UserDetailsService userDetailsService() {
        return new IdentityServiceUserDetailsService(
          this.identityService);
    }
    // spring security configuration
}

4. 使用 Spring Security 进行身份管理

如果我们已经使用 Spring Security 设置了用户管理,并且我们想将 Activiti 添加到我们的应用程序中,那么我们需要自定义 Activiti 的身份管理。

为此,我们必须扩展两个主要类:UserEntityManagerImplGroupEntityManagerImpl,它们处理用户和组。

让我们更详细地看一下其中的每一个。

4.1. 扩展UserEntityManagerImpl

让我们创建自己的类来扩展UserEntityManagerImpl类:

public class SpringSecurityUserManager extends UserEntityManagerImpl {
    private JdbcUserDetailsManager userManager;
    public SpringSecurityUserManager(
      ProcessEngineConfigurationImpl processEngineConfiguration, 
      UserDataManager userDataManager, 
      JdbcUserDetailsManager userManager) {
 
        super(processEngineConfiguration, userDataManager);
        this.userManager = userManager;
    }
    
    // ...
}

此类需要上述形式的构造函数,以及 Spring Security 用户管理器。在我们的例子中,我们使用了数据库支持的UserDetailsManager

我们要覆盖的主要方法是那些处理用户检索的方法:findById()findUserByQueryCriteria()findGroupsByUser()

findById()方法使用JdbcUserDetailsManager查找UserDetails对象并将其转换为User对象:

@Override
public UserEntity findById(String userId) {
    UserDetails userDetails = userManager.loadUserByUsername(userId);
    if (userDetails != null) {
        UserEntityImpl user = new UserEntityImpl();
        user.setId(userId);
        return user;
    }
    return null;
}

接下来,findGroupsByUser()方法查找用户的所有 Spring Security 权限并返回Group对象列表:

public List<Group> findGroupsByUser(String userId) {
    UserDetails userDetails = userManager.loadUserByUsername(userId);
    if (userDetails != null) {
        return userDetails.getAuthorities().stream()
          .map(a -> {
            Group g = new GroupEntityImpl();
            g.setId(a.getAuthority());
            return g;
          })
          .collect(Collectors.toList());
    }
    return null;
}

findUserByQueryCriteria()方法基于具有多个属性的UserQueryImpl对象,我们将从中提取组 id 和用户 id,因为它们在 Spring Security 中具有对应关系:

@Override
public List<User> findUserByQueryCriteria(
  UserQueryImpl query, Page page) {
    // ...
}

此方法遵循与上述类似的原则,通过从UserDetails对象创建User对象。

同样,我们有*findUserCountByQueryCriteria()*方法:

public long findUserCountByQueryCriteria(
  UserQueryImpl query) {
 
    return findUserByQueryCriteria(query, null).size();
}

*checkPassword()*方法应该总是返回 true,因为密码验证不是由 Activiti 完成的:

@Override
public Boolean checkPassword(String userId, String password) {
    return true;
}

对于其他方法,例如那些处理更新用户的方法,我们只会抛出一个异常,因为这是由 Spring Security 处理的:

public User createNewUser(String userId) {
    throw new UnsupportedOperationException("This operation is not supported!");
}

4.2. 扩展GroupEntityManagerImpl

SpringSecurityGroupManager类似于用户管理器类,除了它处理用户组的事实:

public class SpringSecurityGroupManager extends GroupEntityManagerImpl {
    private JdbcUserDetailsManager userManager;
    public SpringSecurityGroupManager(ProcessEngineConfigurationImpl 
      processEngineConfiguration, GroupDataManager groupDataManager) {
        super(processEngineConfiguration, groupDataManager);
    }
    // ...
}

这里*要覆盖的主要方法是*findGroupsByUser()方法:

@Override
public List<Group> findGroupsByUser(String userId) {
    UserDetails userDetails = userManager.loadUserByUsername(userId);
    if (userDetails != null) {
        return userDetails.getAuthorities().stream()
          .map(a -> {
            Group g = new GroupEntityImpl();
            g.setId(a.getAuthority());
            return g;
          })
          .collect(Collectors.toList());
    }
    return null;
}

该方法检索 Spring Security 用户的权限并将其转换为Group对象列表。

基于此,我们还可以重写*findGroupByQueryCriteria()findGroupByQueryCriteriaCount()*方法:

@Override
public List<Group> findGroupByQueryCriteria(GroupQueryImpl query, Page page) {
    if (query.getUserId() != null) {
        return findGroupsByUser(query.getUserId());
    }
    return null;
}
@Override
public long findGroupCountByQueryCriteria(GroupQueryImpl query) {
    return findGroupByQueryCriteria(query, null).size();
}

可以覆盖更新组的其他方法以引发异常:

public Group createNewGroup(String groupId) {
    throw new UnsupportedOperationException("This operation is not supported!");
}

4.3. 流程引擎配置

在定义了两个身份管理器类之后,我们需要将它们连接到配置中。

spring 启动器为我们自动配置SpringProcessEngineConfiguration。要修改它,我们可以使用InitializingBean

@Autowired
private SpringProcessEngineConfiguration processEngineConfiguration;
@Autowired
private JdbcUserDetailsManager userManager;
@Bean
InitializingBean processEngineInitializer() {
    return new InitializingBean() {
        public void afterPropertiesSet() throws Exception {
            processEngineConfiguration.setUserEntityManager(
              new SpringSecurityUserManager(processEngineConfiguration, 
              new MybatisUserDataManager(processEngineConfiguration), userManager));
            processEngineConfiguration.setGroupEntityManager(
              new SpringSecurityGroupManager(processEngineConfiguration, 
              new MybatisGroupDataManager(processEngineConfiguration)));
            }
        };
    }

在这里,现有的processEngineConfiguration被修改为使用我们的自定义身份管理器。

如果我们想在Activiti中设置当前用户,可以使用方法:

identityService.setAuthenticatedUserId(userId);

请记住,这会设置一个ThreadLocal属性,因此每个线程的值都不同。