diff --git a/spring-activiti/pom.xml b/spring-activiti/pom.xml
index c5289b20a6..f6f992b7c0 100644
--- a/spring-activiti/pom.xml
+++ b/spring-activiti/pom.xml
@@ -19,9 +19,11 @@
+ com.example.activitiwithspring.ActivitiWithSpringApplication
UTF-8
UTF-8
1.8
+ 6.0.0
@@ -30,9 +32,14 @@
activiti-spring-boot-starter-basic
6.0.0
+
+ org.activiti
+ activiti-spring-boot-starter-security
+ ${activiti.version}
+
org.springframework.boot
- spring-boot-starter-web
+ spring-boot-starter-thymeleaf
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security.rar b/spring-activiti/src/main/java/com/baeldung/activiti/security.rar
new file mode 100644
index 0000000000..38c4946168
Binary files /dev/null and b/spring-activiti/src/main/java/com/baeldung/activiti/security.rar differ
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security/config/MvcConfig.java b/spring-activiti/src/main/java/com/baeldung/activiti/security/config/MvcConfig.java
new file mode 100644
index 0000000000..f9394742cd
--- /dev/null
+++ b/spring-activiti/src/main/java/com/baeldung/activiti/security/config/MvcConfig.java
@@ -0,0 +1,20 @@
+package com.baeldung.activiti.security.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@Configuration
+@EnableWebMvc
+public class MvcConfig extends WebMvcConfigurerAdapter {
+
+ @Override
+ public void addViewControllers(ViewControllerRegistry registry) {
+ registry.addViewController("/login")
+ .setViewName("login");
+ registry.addViewController("/homepage")
+ .setViewName("homepage");
+ }
+
+}
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security/config/ProcessController.java b/spring-activiti/src/main/java/com/baeldung/activiti/security/config/ProcessController.java
new file mode 100644
index 0000000000..671b246328
--- /dev/null
+++ b/spring-activiti/src/main/java/com/baeldung/activiti/security/config/ProcessController.java
@@ -0,0 +1,54 @@
+package com.baeldung.activiti.security.config;
+
+import java.util.List;
+
+import org.activiti.engine.IdentityService;
+import org.activiti.engine.RuntimeService;
+import org.activiti.engine.TaskService;
+import org.activiti.engine.runtime.ProcessInstance;
+import org.activiti.engine.task.Task;
+import org.activiti.spring.SpringProcessEngineConfiguration;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class ProcessController {
+
+ @Autowired
+ private RuntimeService runtimeService;
+
+ @Autowired
+ private TaskService taskService;
+
+ @Autowired
+ private IdentityService identityService;
+
+ @Autowired
+ SpringProcessEngineConfiguration config;
+
+ @GetMapping("/protected-process")
+ public String startProcess() {
+
+ String userId = SecurityContextHolder.getContext()
+ .getAuthentication()
+ .getName();
+
+ identityService.setAuthenticatedUserId(userId);
+
+ ProcessInstance pi = runtimeService.startProcessInstanceByKey("protected-process");
+
+ List usertasks = taskService.createTaskQuery()
+ .processInstanceId(pi.getId())
+ .list();
+
+ taskService.complete(usertasks.iterator()
+ .next()
+ .getId());
+
+ return "Process started. Number of currently running process instances = " + runtimeService.createProcessInstanceQuery()
+ .count();
+ }
+
+}
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security/config/SpringSecurityGroupManager.java b/spring-activiti/src/main/java/com/baeldung/activiti/security/config/SpringSecurityGroupManager.java
new file mode 100644
index 0000000000..00fc674e22
--- /dev/null
+++ b/spring-activiti/src/main/java/com/baeldung/activiti/security/config/SpringSecurityGroupManager.java
@@ -0,0 +1,86 @@
+package com.baeldung.activiti.security.config;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.activiti.engine.identity.Group;
+import org.activiti.engine.identity.GroupQuery;
+import org.activiti.engine.impl.GroupQueryImpl;
+import org.activiti.engine.impl.Page;
+import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
+import org.activiti.engine.impl.persistence.entity.GroupEntityImpl;
+import org.activiti.engine.impl.persistence.entity.GroupEntityManagerImpl;
+import org.activiti.engine.impl.persistence.entity.data.GroupDataManager;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.provisioning.JdbcUserDetailsManager;
+
+public class SpringSecurityGroupManager extends GroupEntityManagerImpl {
+
+ private JdbcUserDetailsManager userManager;
+
+ public SpringSecurityGroupManager(ProcessEngineConfigurationImpl processEngineConfiguration, GroupDataManager groupDataManager) {
+ super(processEngineConfiguration, groupDataManager);
+ }
+
+ @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();
+ }
+
+ @Override
+ public List findGroupsByUser(String userId) {
+ UserDetails userDetails = userManager.loadUserByUsername(userId);
+ System.out.println("group manager");
+ if (userDetails != null) {
+ List groups = userDetails.getAuthorities()
+ .stream()
+ .map(a -> a.getAuthority())
+ .map(a -> {
+ Group g = new GroupEntityImpl();
+ g.setId(a);
+ return g;
+ })
+ .collect(Collectors.toList());
+ return groups;
+ }
+ return null;
+ }
+
+ public void setUserManager(JdbcUserDetailsManager userManager) {
+ this.userManager = userManager;
+ }
+
+ public Group createNewGroup(String groupId) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+
+ }
+
+ @Override
+ public void delete(String groupId) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+
+ }
+
+ public GroupQuery createNewGroupQuery() {
+ throw new UnsupportedOperationException("This operation is not supported!");
+ }
+
+ public List findGroupsByNativeQuery(Map parameterMap, int firstResult, int maxResults) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+ }
+
+ public long findGroupCountByNativeQuery(Map parameterMap) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+ }
+
+}
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security/config/SpringSecurityUserManager.java b/spring-activiti/src/main/java/com/baeldung/activiti/security/config/SpringSecurityUserManager.java
new file mode 100644
index 0000000000..ce9863eb6c
--- /dev/null
+++ b/spring-activiti/src/main/java/com/baeldung/activiti/security/config/SpringSecurityUserManager.java
@@ -0,0 +1,144 @@
+package com.baeldung.activiti.security.config;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.activiti.engine.identity.Group;
+import org.activiti.engine.identity.User;
+import org.activiti.engine.identity.UserQuery;
+import org.activiti.engine.impl.Page;
+import org.activiti.engine.impl.UserQueryImpl;
+import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
+import org.activiti.engine.impl.persistence.entity.GroupEntityImpl;
+import org.activiti.engine.impl.persistence.entity.UserEntity;
+import org.activiti.engine.impl.persistence.entity.UserEntityImpl;
+import org.activiti.engine.impl.persistence.entity.UserEntityManagerImpl;
+import org.activiti.engine.impl.persistence.entity.data.UserDataManager;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.provisioning.JdbcUserDetailsManager;
+
+public class SpringSecurityUserManager extends UserEntityManagerImpl {
+
+ private JdbcUserDetailsManager userManager;
+
+ public SpringSecurityUserManager(ProcessEngineConfigurationImpl processEngineConfiguration, UserDataManager userDataManager, JdbcUserDetailsManager userManager) {
+ super(processEngineConfiguration, userDataManager);
+ this.userManager = userManager;
+ }
+
+ @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;
+
+ }
+
+ @Override
+ public List findUserByQueryCriteria(UserQueryImpl query, Page page) {
+ List users = null;
+ if (query.getGroupId() != null) {
+ users = userManager.findUsersInGroup(query.getGroupId())
+ .stream()
+ .map(username -> {
+ User user = new UserEntityImpl();
+ user.setId(username);
+ return user;
+ })
+ .collect(Collectors.toList());
+ if (page != null) {
+ return users.subList(page.getFirstResult(), page.getFirstResult() + page.getMaxResults());
+
+ }
+ return users;
+ }
+
+ if (query.getId() != null) {
+ UserDetails userDetails = userManager.loadUserByUsername(query.getId());
+ if (userDetails != null) {
+ UserEntityImpl user = new UserEntityImpl();
+ user.setId(query.getId());
+ return Collections.singletonList(user);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Boolean checkPassword(String userId, String password) {
+ return true;
+ }
+
+ public void setUserManager(JdbcUserDetailsManager userManager) {
+ this.userManager = userManager;
+ }
+
+ public User createNewUser(String userId) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+ }
+
+ public void updateUser(User updatedUser) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+
+ }
+
+ public void delete(UserEntity userEntity) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+
+ }
+
+ @Override
+ public void deletePicture(User user) {
+ UserEntity userEntity = (UserEntity) user;
+ if (userEntity.getPictureByteArrayRef() != null) {
+ userEntity.getPictureByteArrayRef()
+ .delete();
+ }
+ }
+
+ public void delete(String userId) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+
+ }
+
+ public long findUserCountByQueryCriteria(UserQueryImpl query) {
+ return findUserByQueryCriteria(query, null).size();
+ }
+
+ public List findGroupsByUser(String userId) {
+ UserDetails userDetails = userManager.loadUserByUsername(userId);
+ if (userDetails != null) {
+ List groups = userDetails.getAuthorities()
+ .stream()
+ .map(a -> a.getAuthority())
+ .map(a -> {
+ Group g = new GroupEntityImpl();
+ g.setId(a);
+ return g;
+ })
+ .collect(Collectors.toList());
+ return groups;
+ }
+ return null;
+ }
+
+ public UserQuery createNewUserQuery() {
+ throw new UnsupportedOperationException("This operation is not supported!");
+ }
+
+ public List findUsersByNativeQuery(Map parameterMap, int firstResult, int maxResults) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+ }
+
+ public long findUserCountByNativeQuery(Map parameterMap) {
+ throw new UnsupportedOperationException("This operation is not supported!");
+
+ }
+
+}
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security/withactiviti/SecurityConfig.java b/spring-activiti/src/main/java/com/baeldung/activiti/security/withactiviti/SecurityConfig.java
new file mode 100644
index 0000000000..f471600553
--- /dev/null
+++ b/spring-activiti/src/main/java/com/baeldung/activiti/security/withactiviti/SecurityConfig.java
@@ -0,0 +1,47 @@
+package com.baeldung.activiti.security.withactiviti;
+
+import org.activiti.engine.IdentityService;
+import org.activiti.spring.security.IdentityServiceUserDetailsService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+@Configuration
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ protected void configure(HttpSecurity http) throws Exception {
+ http.antMatcher("/**")
+ .authorizeRequests()
+ .antMatchers("/protected-process*")
+ .authenticated()
+ .anyRequest()
+ .permitAll()
+ .and()
+ .formLogin()
+ .loginPage("/login")
+ .defaultSuccessUrl("/homepage")
+ .failureUrl("/login?error=true")
+ .and()
+ .csrf()
+ .disable()
+ .logout()
+ .logoutSuccessUrl("/login");
+ }
+
+ @Autowired
+ private IdentityService identityService;
+
+ @Bean
+ public IdentityServiceUserDetailsService userDetailsService() {
+ return new IdentityServiceUserDetailsService(identityService);
+ }
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ auth.userDetailsService(userDetailsService());
+ }
+
+}
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security/withactiviti/SpringSecurityActivitiApplication.java b/spring-activiti/src/main/java/com/baeldung/activiti/security/withactiviti/SpringSecurityActivitiApplication.java
new file mode 100644
index 0000000000..2270a4d684
--- /dev/null
+++ b/spring-activiti/src/main/java/com/baeldung/activiti/security/withactiviti/SpringSecurityActivitiApplication.java
@@ -0,0 +1,34 @@
+package com.baeldung.activiti.security.withactiviti;
+
+import org.activiti.engine.IdentityService;
+import org.activiti.engine.identity.Group;
+import org.activiti.engine.identity.User;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+@SpringBootApplication(scanBasePackages = { "com.baeldung.activiti.security.config", "com.baeldung.activiti.security.withactiviti" })
+public class SpringSecurityActivitiApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringSecurityActivitiApplication.class, args);
+ }
+
+ @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());
+ }
+ };
+ }
+}
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security/withspring/ActivitiSpringSecurityApplication.java b/spring-activiti/src/main/java/com/baeldung/activiti/security/withspring/ActivitiSpringSecurityApplication.java
new file mode 100644
index 0000000000..5878a5d678
--- /dev/null
+++ b/spring-activiti/src/main/java/com/baeldung/activiti/security/withspring/ActivitiSpringSecurityApplication.java
@@ -0,0 +1,39 @@
+package com.baeldung.activiti.security.withspring;
+
+import org.activiti.engine.impl.persistence.entity.data.impl.MybatisGroupDataManager;
+import org.activiti.engine.impl.persistence.entity.data.impl.MybatisUserDataManager;
+import org.activiti.spring.SpringProcessEngineConfiguration;
+import org.activiti.spring.boot.SecurityAutoConfiguration;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.provisioning.JdbcUserDetailsManager;
+
+import com.baeldung.activiti.security.config.SpringSecurityGroupManager;
+import com.baeldung.activiti.security.config.SpringSecurityUserManager;
+
+@SpringBootApplication(exclude = SecurityAutoConfiguration.class, scanBasePackages = { "com.baeldung.activiti.security.config", "com.baeldung.activiti.security.withspring" })
+public class ActivitiSpringSecurityApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ActivitiSpringSecurityApplication.class, args);
+ }
+
+ @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)));
+ }
+ };
+ }
+}
diff --git a/spring-activiti/src/main/java/com/baeldung/activiti/security/withspring/SecurityConfig.java b/spring-activiti/src/main/java/com/baeldung/activiti/security/withspring/SecurityConfig.java
new file mode 100644
index 0000000000..df1991c3e4
--- /dev/null
+++ b/spring-activiti/src/main/java/com/baeldung/activiti/security/withspring/SecurityConfig.java
@@ -0,0 +1,50 @@
+package com.baeldung.activiti.security.withspring;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.provisioning.JdbcUserDetailsManager;
+
+@Configuration
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private DataSource dataSource;
+
+ protected void configure(HttpSecurity http) throws Exception {
+ http.antMatcher("/**")
+ .authorizeRequests()
+ .antMatchers("/protected-process*")
+ .authenticated()
+ .anyRequest()
+ .permitAll()
+ .and()
+ .formLogin()
+ .loginPage("/login")
+ .defaultSuccessUrl("/homepage")
+ .failureUrl("/login?error=true")
+ .and()
+ .csrf()
+ .disable()
+ .logout()
+ .logoutSuccessUrl("/login");
+ }
+
+ @Bean
+ public JdbcUserDetailsManager userDetailsManager() {
+ JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
+ manager.setDataSource(dataSource);
+ return manager;
+ }
+
+ @Autowired
+ public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+ auth.userDetailsService(userDetailsManager());
+ }
+
+}
diff --git a/spring-activiti/src/main/resources/data.sql b/spring-activiti/src/main/resources/data.sql
new file mode 100644
index 0000000000..cb9b72617a
--- /dev/null
+++ b/spring-activiti/src/main/resources/data.sql
@@ -0,0 +1,3 @@
+insert into users(username, password, enabled) values ('spring_user', 'pass', true);
+
+insert into authorities(username, authority) values ('spring_user','ROLE_USER');
\ No newline at end of file
diff --git a/spring-activiti/src/main/resources/processes/protected-process.bpmn20.xml b/spring-activiti/src/main/resources/processes/protected-process.bpmn20.xml
new file mode 100644
index 0000000000..b7e04515cd
--- /dev/null
+++ b/spring-activiti/src/main/resources/processes/protected-process.bpmn20.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+ ROLE_USER
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-activiti/src/main/resources/schema.sql b/spring-activiti/src/main/resources/schema.sql
new file mode 100644
index 0000000000..bf882895fd
--- /dev/null
+++ b/spring-activiti/src/main/resources/schema.sql
@@ -0,0 +1,3 @@
+create table users(username varchar(255), password varchar(255), enabled boolean);
+
+create table authorities(username varchar(255),authority varchar(255));
\ No newline at end of file
diff --git a/spring-activiti/src/main/resources/templates/login.html b/spring-activiti/src/main/resources/templates/login.html
new file mode 100644
index 0000000000..53077fd5f3
--- /dev/null
+++ b/spring-activiti/src/main/resources/templates/login.html
@@ -0,0 +1,21 @@
+
+
+
+ Login
+
+
+
\ No newline at end of file
diff --git a/spring-activiti/src/test/java/com/example/activitiwithspring/ActivitiSpringSecurityIntegrationTest.java b/spring-activiti/src/test/java/com/example/activitiwithspring/ActivitiSpringSecurityIntegrationTest.java
new file mode 100644
index 0000000000..c2eeb96555
--- /dev/null
+++ b/spring-activiti/src/test/java/com/example/activitiwithspring/ActivitiSpringSecurityIntegrationTest.java
@@ -0,0 +1,31 @@
+package com.example.activitiwithspring;
+
+import org.activiti.engine.IdentityService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.context.web.WebAppConfiguration;
+
+import com.baeldung.activiti.security.withspring.ActivitiSpringSecurityApplication;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = ActivitiSpringSecurityApplication.class)
+@WebAppConfiguration
+public class ActivitiSpringSecurityIntegrationTest {
+ @Autowired
+ private IdentityService identityService;
+
+ @Test
+ public void whenUserExists_thenOk() {
+ identityService.setUserPicture("spring_user", null);
+ }
+
+ @Test(expected = UsernameNotFoundException.class)
+ public void whenUserNonExistent_thenSpringException() {
+ identityService.setUserPicture("user3", null);
+ }
+
+}