Activiti with Spring Security – 含有Spring Security的Activiti

最后修改: 2017年 11月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Activiti is an open-source BPM (Business Process Management) system. For an introduction, check our Guide to Activiti with Java.

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

Both Activiti and the Spring framework provide their own identity management. However, in an application that integrates both projects, we may want to combine the two into a single user management process.

Activiti和Spring框架都提供了各自的身份管理。然而,在一个整合了这两个项目的应用程序中,我们可能希望将这两个项目合并为一个用户管理流程。

In the following, we’ll explore two possibilities to achieve this: one is by providing an Activiti-backed user service for Spring Security and the other by plugging a Spring Security user source into the Activiti identity management.

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

2. Maven Dependencies

2.Maven的依赖性

To set up Activiti in a Spring Boot project, check out our previous article. In addition to activiti-spring-boot-starter-basic, we’ll also need the activiti-spring-boot-starter-security dependency:

要在 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. Identity Management Using Activiti

3.使用Activiti的身份管理

For this scenario, the Activiti starters provide a Spring Boot auto-configuration class which secures all REST endpoints with HTTP Basic authentication.

对于这种情况,Activiti启动器提供了一个Spring Boot自动配置类,它用HTTP Basic认证来保护所有REST端点。

The auto-configuration also creates a UserDetailsService bean of class IdentityServiceUserDetailsService.

自动配置还创建了一个UserDetailsServiceBean,其类别为IdentityServiceUserDetailsService。

The class implements the Spring interface UserDetailsService and overrides the loadUserByUsername() method. This method retrieves an Activiti User object with the given id and uses it to create a Spring UserDetails object.

该类实现了Spring接口UserDetailsService并重写了loadUserByUsername()方法。该方法以给定的id检索Activiti User对象,并使用它来创建一个Spring UserDetails对象。

Also, the Activiti Group object corresponds to a Spring user role.

另外,Activiti Group 对象与Spring用户角色相对应。

What this means is that when we login to the Spring Security application, we’ll use Activiti credentials.

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

3.1. Setting Up Activiti Users

3.1.设置Activiti用户

First, let’s create a user in an InitializingBean defined in the main @SpringBootApplication class, using the IdentityService:

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

@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());
        }
    };
}

You’ll notice that since this will be used by Spring Security, the Group object name has to be of the form “ROLE_X”.

你会注意到,由于这将被Spring Security使用,Group对象的name必须是“ROLE_X”.形式。

3.2. Spring Security Configuration

3.2.Spring安全配置

If we want to use a different security configuration instead of the HTTP Basic authentication, first we have to exclude the auto-configuration:

如果我们想使用不同的安全配置来代替HTTP Basic认证,首先我们必须排除自动配置。

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

Then, we can provide our own Spring Security configuration class that uses the IdentityServiceUserDetailsService to retrieve users from the Activiti data source:

然后,我们可以提供自己的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. Identity Management Using Spring Security

4.使用Spring Security的身份管理

If we already have user management set up with Spring Security and we want to add Activiti to our application, then we need to customize Activiti’s identity management.

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

For this purpose, there’re two main classes we have to extend: UserEntityManagerImpl and GroupEntityManagerImpl which handle users and groups.

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

Let’s take a look at each of these in more detail.

让我们更详细地看看每一个问题。

4.1. Extending UserEntityManagerImpl

4.1.扩展UserEntityManagerImpl

Let’s create our own class which extends the UserEntityManagerImpl class:

让我们创建自己的类,它扩展了UserEntityManagerImpl类。

public class SpringSecurityUserManager extends UserEntityManagerImpl {

    private JdbcUserDetailsManager userManager;

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

This class needs a constructor of the form above, as well as the Spring Security user manager. In our case, we’ve used a database-backed UserDetailsManager.

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

The main methods we want to override are those that handle user retrieval: findById(), findUserByQueryCriteria() and findGroupsByUser().

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

The findById() method uses the JdbcUserDetailsManager to find a UserDetails object and transform it into a User object:

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;
}

Next, the findGroupsByUser() method finds all the Spring Security authorities of a user and returns a List of Group objects:

接下来,findGroupsByUser()方法找到用户的所有Spring Security权限,并返回ListGroup对象。

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;
}

The findUserByQueryCriteria() method is based on a UserQueryImpl object with multiple properties, from which we’ll extract the group id and user id, as they have correspondents in Spring Security:

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

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

This method follows a similar principle to the ones above, by creating User objects from UserDetails objects. See the GitHub link at the end for the full implementation.

这个方法的原理与上面的类似,从UserDetails对象中创建User对象。完整的实现见最后的GitHub链接。

Similarly, we’ve got the findUserCountByQueryCriteria() method:

同样地,我们有findUserCountByQueryCriteria()方法。

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

The checkPassword() method should always return true as the password verification is not done by Activiti:

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

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

For other methods, such as those dealing with updating users, we’ll just throw an exception since this is handled by Spring Security:

对于其他方法,比如那些处理更新用户的方法,我们将直接抛出一个异常,因为这是由Spring Security处理的。

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

4.2. Extend the GroupEntityManagerImpl

4.2.扩展GroupEntityManagerImpl

The SpringSecurityGroupManager is similar to the user manager class, except the fact it deals with user groups:

SpringSecurityGroupManager与用户管理器类类似,只是它处理的是用户组。

public class SpringSecurityGroupManager extends GroupEntityManagerImpl {

    private JdbcUserDetailsManager userManager;

    public SpringSecurityGroupManager(ProcessEngineConfigurationImpl 
      processEngineConfiguration, GroupDataManager groupDataManager) {
        super(processEngineConfiguration, groupDataManager);
    }

    // ...
}

Here the main method to override is the findGroupsByUser() method:

这里要覆盖的主要方法是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;
}

The method retrieves a Spring Security user’s authorities and transforms them to a list of Group objects.

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

Based on this, we can also override the findGroupByQueryCriteria() and findGroupByQueryCriteriaCount() methods:

基于此,我们也可以覆盖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();
}

Other methods that update groups can be overridden to throw an exception:

其他更新组的方法可以被重载以抛出一个异常。

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

4.3. Process Engine Configuration

4.3.过程引擎配置

After defining the two identity manager classes, we need to wire them into the configuration.

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

The spring starters auto-configure a SpringProcessEngineConfiguration for us. To modify this, we can use an InitializingBean:

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)));
            }
        };
    }

Here, the existing processEngineConfiguration is modified to use our custom identity managers.

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

If we want to set the current user in Activiti, we can use the method:

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

identityService.setAuthenticatedUserId(userId);

Keep in mind that this sets a ThreadLocal property, so the value is different for every thread.

请记住,这设置了一个ThreadLocal属性,所以每个线程的值都不同。

5. Conclusion

5.结论

In this article, we’ve seen the two ways we can integrate Activiti with Spring Security.

在这篇文章中,我们已经看到了将Activiti与Spring Security整合的两种方式。

The full source code can be found over on GitHub.

完整的源代码可以在GitHub上找到over