Atividades com Spring Security
1. Visão geral
O Activiti é um sistema BPM (Business Process Management) de código aberto. Para uma introdução, verifique nossoGuide to Activiti with Java.
A Activiti e a estrutura Spring fornecem seu próprio gerenciamento de identidade. No entanto,in an application that integrates both projects, we may want to combine the two into a single user management process.
A seguir, vamos explorar duas possibilidades para conseguir isso: uma é fornecendo um serviço de usuário apoiado pela Activiti para Spring Security e a outra conectando uma fonte de usuário Spring Security no gerenciamento de identidade da Activiti.
2. Dependências do Maven
Para configurar o Activiti em um projeto Spring Boot, verifiqueour previous article. Além deactiviti-spring-boot-starter-basic,, também precisaremos da dependênciaactiviti-spring-boot-starter-security:
org.activiti
activiti-spring-boot-starter-security
6.0.0
3. Gerenciamento de identidade usando Activiti
Para este cenário, os iniciadores Activiti fornecem uma classe de configuração automática Spring Boot que protege todos os endpoints REST com autenticaçãoHTTP Basic.
A configuração automática também cria um beanUserDetailsService de classeIdentityServiceUserDetailsService.
A classe implementa a interface SpringUserDetailsServicee substitui o métodoloadUserByUsername(). Este método recupera um objeto ActivitiUser com oid fornecido e o usa para criar um objeto SpringUserDetails.
Além disso, o objeto ActivitiGroup corresponde a uma função de usuário Spring.
O que isso significa é que, quando fizermos login no aplicativo Spring Security, usaremos as credenciais da Activiti.
3.1. Configurando usuários do Activiti
Primeiro, vamos criar um usuário em umInitializingBean definido na classe@SpringBootApplication principal, usando oIdentityService:
@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());
}
};
}
Você notará quesince this will be used by Spring Security, the Group object name has to be of the form “ROLE_X”.
3.2. Configuração de segurança da primavera
Se queremos usar uma configuração de segurança diferente em vez da autenticação HTTP Basic, primeiro temos que excluir a configuração automática:
@SpringBootApplication(
exclude = org.activiti.spring.boot.SecurityAutoConfiguration.class)
public class ActivitiSpringSecurityApplication {
// ...
}
Em seguida, podemos fornecer nossa própria classe de configuração Spring Security que usaIdentityServiceUserDetailsService para recuperar usuários da fonte de dados 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. Gerenciamento de identidade usando Spring Security
Se já temos o gerenciamento de usuários configurado com Spring Security e queremos adicionar o Activiti ao nosso aplicativo, então precisamos personalizar o gerenciamento de identidade do Activiti.
Para este propósito, existem duas classes principais que devemos estender:UserEntityManagerImpl eGroupEntityManagerImpl que lidam com usuários e grupos.
Vamos dar uma olhada em cada um deles com mais detalhes.
4.1. EstendendoUserEntityManagerImpl
Vamos criar nossa própria classe que estende a classeUserEntityManagerImpl:
public class SpringSecurityUserManager extends UserEntityManagerImpl {
private JdbcUserDetailsManager userManager;
public SpringSecurityUserManager(
ProcessEngineConfigurationImpl processEngineConfiguration,
UserDataManager userDataManager,
JdbcUserDetailsManager userManager) {
super(processEngineConfiguration, userDataManager);
this.userManager = userManager;
}
// ...
}
Esta classe precisa de um construtor do formulário acima, bem como do gerenciador de usuários do Spring Security. Em nosso caso, usamos um banco de dadosUserDetailsManager.
Os principais métodos que queremos substituir são aqueles que tratam da recuperação do usuário:findById(),findUserByQueryCriteria()efindGroupsByUser().
O métodofindById() usaJdbcUserDetailsManager para encontrar um objetoUserDetails e transformá-lo em um objetoUser:
@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;
}
Em seguida, o métodofindGroupsByUser() encontra todas as autoridades de Spring Security de um usuário e retornaList de objetosGroup:
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;
}
O métodofindUserByQueryCriteria() é baseado em um objetoUserQueryImpl com várias propriedades, das quais extrairemos o id do grupo e do usuário, pois eles têm correspondentes no Spring Security:
@Override
public List findUserByQueryCriteria(
UserQueryImpl query, Page page) {
// ...
}
Este método segue um princípio semelhante aos anteriores, criando objetosUser a partir de objetosUserDetails. Veja o link do GitHub no final para a implementação completa.
Da mesma forma, temos o métodofindUserCountByQueryCriteria():
public long findUserCountByQueryCriteria(
UserQueryImpl query) {
return findUserByQueryCriteria(query, null).size();
}
O métodocheckPassword() deve sempre retornar verdadeiro, pois a verificação de senha não é feita pela Activiti:
@Override
public Boolean checkPassword(String userId, String password) {
return true;
}
Para outros métodos, como aqueles que lidam com a atualização de usuários, lançaremos apenas uma exceção, pois isso é tratado pelo Spring Security:
public User createNewUser(String userId) {
throw new UnsupportedOperationException("This operation is not supported!");
}
4.2. Estenda oGroupEntityManagerImpl
OSpringSecurityGroupManager é semelhante à classe do gerenciador de usuários, exceto pelo fato de que lida com grupos de usuários:
public class SpringSecurityGroupManager extends GroupEntityManagerImpl {
private JdbcUserDetailsManager userManager;
public SpringSecurityGroupManager(ProcessEngineConfigurationImpl
processEngineConfiguration, GroupDataManager groupDataManager) {
super(processEngineConfiguration, groupDataManager);
}
// ...
}
Aquithe 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;
}
O método recupera as autoridades de um usuário do Spring Security e as transforma em uma lista de objetosGroup.
Com base nisso, também podemos substituir os métodosfindGroupByQueryCriteria()efindGroupByQueryCriteriaCount():
@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();
}
Outros métodos que atualizam grupos podem ser substituídos para gerar uma exceção:
public Group createNewGroup(String groupId) {
throw new UnsupportedOperationException("This operation is not supported!");
}
4.3. Configuração do Process Engine
Após definir as duas classes do gerenciador de identidades, precisamos conectá-las à configuração.
Os arrancadores de mola auto-configuram aSpringProcessEngineConfiguration para nós. Para modificar isso, podemos usar umInitializingBean:
@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)));
}
};
}
Aqui, oprocessEngineConfiguration existente é modificado para usar nossos gerenciadores de identidade personalizados.
Se quisermos definir o usuário atual no Activiti, podemos usar o método:
identityService.setAuthenticatedUserId(userId);
Lembre-se de que isso define uma propriedadeThreadLocal, então o valor é diferente para cada thread.
5. Conclusão
Neste artigo, vimos as duas maneiras de integrar o Activiti com o Spring Security.
O código-fonte completo pode ser encontradoover on GitHub.