Activiti avec Spring Security

Activiti avec Spring Security

1. Vue d'ensemble

Activiti est un système BPM (Business Process Management) open source. Pour une introduction, consultez nosGuide to Activiti with Java.

Activiti et le cadre Spring fournissent tous deux leur propre gestion des identités. Cependant,in an application that integrates both projects, we may want to combine the two into a single user management process.

Dans ce qui suit, nous explorerons deux possibilités pour y parvenir: l'une consiste à fournir un service utilisateur soutenu par Activiti pour Spring Security et l'autre en connectant une source d'utilisateurs Spring Security à la gestion des identités Activiti.

2. Dépendances Maven

Pour configurer Activiti dans un projet Spring Boot, consultezour previous article. En plus deactiviti-spring-boot-starter-basic,, nous aurons également besoin de la dépendanceactiviti-spring-boot-starter-security:


    org.activiti
    activiti-spring-boot-starter-security
    6.0.0

3. Gestion des identités avec Activiti

Pour ce scénario, les démarreurs Activiti fournissent une classe de configuration automatique Spring Boot qui sécurise tous les points de terminaison REST avec l'authentificationHTTP Basic.

L'auto-configuration crée également un beanUserDetailsService de classeIdentityServiceUserDetailsService.

La classe implémente l'interface SpringUserDetailsService et remplace la méthodeloadUserByUsername(). Cette méthode récupère un objet ActivitiUser avec lesid donnés et l'utilise pour créer un objet SpringUserDetails.

De plus, l'objet ActivitiGroup correspond à un rôle d'utilisateur Spring.

Cela signifie que lorsque nous nous connecterons à l'application Spring Security, nous utiliserons les informations d'identification Activiti.

3.1. Configurer les utilisateurs Activiti

Commençons par créer un utilisateur dans unInitializingBean défini dans la classe principale@SpringBootApplication, en utilisant lesIdentityService:

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

Vous remarquerez quesince this will be used by Spring Security, the Group object name has to be of the form “ROLE_X”.

3.2. Configuration de sécurité de printemps

Si nous voulons utiliser une configuration de sécurité différente au lieu de l'authentification HTTP Basic, nous devons d'abord exclure la configuration automatique:

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

Ensuite, nous pouvons fournir notre propre classe de configuration Spring Security qui utilise lesIdentityServiceUserDetailsService pour récupérer les utilisateurs de la source de données 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. Gestion des identités à l'aide de Spring Security

Si nous avons déjà configuré la gestion des utilisateurs avec Spring Security et que nous voulons ajouter Activiti à notre application, nous devons personnaliser la gestion des identités d'Activiti.

Pour cela, nous devons étendre deux classes principales:UserEntityManagerImpl etGroupEntityManagerImpl qui gèrent les utilisateurs et les groupes.

Examinons chacun de ces éléments plus en détail.

4.1. Extension deUserEntityManagerImpl

Créons notre propre classe qui étend la classeUserEntityManagerImpl:

public class SpringSecurityUserManager extends UserEntityManagerImpl {

    private JdbcUserDetailsManager userManager;

    public SpringSecurityUserManager(
      ProcessEngineConfigurationImpl processEngineConfiguration,
      UserDataManager userDataManager,
      JdbcUserDetailsManager userManager) {

        super(processEngineConfiguration, userDataManager);
        this.userManager = userManager;
    }

    // ...
}

Cette classe nécessite un constructeur du formulaire ci-dessus, ainsi que le gestionnaire d'utilisateurs Spring Security. Dans notre cas, nous avons utilisé unUserDetailsManager. basé sur une base de données

Les principales méthodes que nous voulons remplacer sont celles qui gèrent la récupération par l'utilisateur:findById(),findUserByQueryCriteria() etfindGroupsByUser().

La méthodefindById() utilise lesJdbcUserDetailsManager pour trouver un objetUserDetails et le transformer en un objetUser:

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

Ensuite, la méthodefindGroupsByUser() trouve toutes les autorités Spring Security d'un utilisateur et renvoie unList des objetsGroup:

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

La méthodefindUserByQueryCriteria() est basée sur un objetUserQueryImpl avec plusieurs propriétés, à partir duquel nous extrairons l'ID de groupe et l'ID utilisateur, car ils ont des correspondants dans Spring Security:

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

Cette méthode suit un principe similaire à ceux ci-dessus, en créant des objetsUser à partir des objetsUserDetails. Voir le lien GitHub à la fin pour la mise en œuvre complète.

De même, nous avons la méthodefindUserCountByQueryCriteria():

public long findUserCountByQueryCriteria(
  UserQueryImpl query) {

    return findUserByQueryCriteria(query, null).size();
}

La méthodecheckPassword() doit toujours renvoyer true car la vérification du mot de passe n'est pas effectuée par Activiti:

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

Pour les autres méthodes, telles que celles traitant de la mise à jour des utilisateurs, nous allons simplement lever une exception car cela est géré par Spring Security:

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

4.2. Étendre lesGroupEntityManagerImpl

LeSpringSecurityGroupManager est similaire à la classe du gestionnaire d'utilisateurs, sauf le fait qu'il traite des groupes d'utilisateurs:

public class SpringSecurityGroupManager extends GroupEntityManagerImpl {

    private JdbcUserDetailsManager userManager;

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

    // ...
}

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

@Override
public List 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;
}

La méthode récupère les autorités d'un utilisateur Spring Security et les transforme en une liste d'objetsGroup.

Sur cette base, nous pouvons également remplacer les méthodesfindGroupByQueryCriteria() etfindGroupByQueryCriteriaCount():

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

D'autres méthodes mettant à jour des groupes peuvent être remplacées pour générer une exception:

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

4.3. Configuration du moteur de processus

Après avoir défini les deux classes du gestionnaire d’identités, nous devons les connecter à la configuration.

Les démarreurs à ressort configurent automatiquement unSpringProcessEngineConfiguration pour nous. Pour modifier cela, nous pouvons utiliser unInitializingBean:

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

Ici, lesprocessEngineConfiguration existants sont modifiés pour utiliser nos gestionnaires d'identité personnalisés.

Si nous voulons définir l’utilisateur actuel dans Activiti, nous pouvons utiliser la méthode suivante:

identityService.setAuthenticatedUserId(userId);

Gardez à l'esprit que cela définit une propriétéThreadLocal, donc la valeur est différente pour chaque thread.

5. Conclusion

Dans cet article, nous avons vu les deux façons d'intégrer Activiti à Spring Security.

Le code source complet peut être trouvéover on GitHub.