Skip to main content

Design Security

Security and Identity Management

Flowable relies on the Spring Security framework to provide authentication and access control capabilities. Spring Security is an open source library that provides connectors and adapters to many security standards such as OAuth, SAML, basic authentication, etc. As with all Spring frameworks, it is designed to be highly customizable and adaptable to many security requirements.

Authentication and Authorization

The out of the box security configuration for Flowable provides a simple configuration that can be extended and adapted according to your needs.

Upon an authentication attempt, the user is loaded through the DesignUserDetailsService (which is called from DaoAuthenticationProvider as this class delegates to an instance of the UserDetailsService) and given that the user exists, an instance of org.springframework.security.core.userdetails.UserDetails is created. The UserDetails object contains the following information:

  • The username and password of the user

  • Granted authorities for the user:

    • Group keys prefixed with GROUP_

    • Tenant id prefixed with TENANT_

After the Spring Security filter chain is executed, the Spring Security Context contains the authenticated user information, which is used for access control or other purposes.

Flowable uses the com.flowable.design.security.spring.DesignSecurityUtils to retrieve all the needed information for the Flowable Authorization. Every action uses com.flowable.design.security.api.DesignSecurityScope to get the required information.

  • DesignSecurityScope#getUserId() provides the id of the current user.

  • DesignSecurityScope#getTenantId() provides the tenant id of the current user.

  • DesignSecurityScope#getGroupKeys() provides the group keys of the current user.

  • DesignSecurityScope#hasAuthority(String) provides a way to check if the current user has the given authority.

  • DesignSecurityScope#hasAdminRights() provides a way to check if the current user has admin rights.

  • DesignSecurityScope#hasSuperAdminRights() provides a way to check if the current user has super admin rights. By default, a user has super admin rights if the user is part of the default tenant and has admin rights.

The DesignSecurityScope is provided via a bean com.flowable.design.security.api.DesignSecurityScopeProvider. The default implementation uses the org.springframework.security.core.Authentication to get access to the relevant information.

  • DesignSecurityScope#getUserId() retrieved via Authentication#getName().

  • DesignSecurityScope#getTenantId() the first GrantedAuthority that is prefixed with TENANT_.

  • DesignSecurityScope#getGroupKeys() all granted GrantedAuthority(ies) that have the GROUP_ prefix

  • SecurityScope#hasAuthority(String) provides a way to check if the current user has the given authority.

For example, if the user has the following GrantedAuthority(ies)

  • GROUP_user

  • GROUP_admin

  • TENANT_acme

  • access-reports

The current user group keys are user and admin, and the current user tenant id is acme.

Administrators

When doing calls over REST Flowable needs to know whether the user doing the request is an admin user or not. The reason for this is that admin users are allowed to access all data (within their tenant). For these users it is not checked if they have access to the data via the IdentityLink(s). Such users are the technical users used by Flowable Control to get data for a particular Flowable instance.

By default, a user is an admin user if they have the access-admin granted authority. However, it is possible to provide a custom logic for determining whether a user is an admin or not. This can be done by implementing your own DesignSecurityScopeProvider and providing your own implementation of the DesignSecurityScope This means that you are in full control to decide how you want to determine what an admin user is.

Super Administrators

Super administrators are admin users which are in the empty or default tenant. These users have access to the data across all tenants in the system. You can also customize this by implementing your own DesignSecurityScopeProvider and providing your own implementation of the DesignSecurityScope

Customizing the Default Configuration

As other Flowable products, the Design Identity Management (IDM) provides extension points to override default beans by custom ones. For instance, if in your project, you provide a custom UserDetailsService, the security auto-configuration (com.flowable.autoconfigure.design.security.DesignSecurityAutoConfiguration) finds the bean and does not use the default bean.

See the following classes for more configuration options:

  • com.flowable.autoconfigure.design.security.DesignSecurityAutoConfiguration

  • com.flowable.design.security.spring.userdetails.DesignUserDetailsService

  • com.flowable.autoconfigure.design.security.FlowableDesignSecurityProperties

In order for Flowable to be able to use the correct userId, group keys, tenant id then a custom bean implementation of com.flowable.design.security.api.DesignSecurityScopeProvider should be provided.

Have a look at Custom Security Authentication for more details about how to customize the authentication and Custom Identity Query Service for more details how to write a custom DesignIdentityQueryService.

Overriding the Default Identity Query Service

The default DesignIdentityQueryService is sufficient if the users are synced in the Flowable Database. However, in most cases you have your own identity store (LDAP, Azure AD, or something else). In this case you would need to provide your own implementation of the DesignIdentityQueryService.

In order for your custom DesignIdentityQueryService to be configured with the DesignEngineConfiguration, you need to use an Consumer as follows:

@Bean
public Consumer<DesignEngineConfiguration> customDesignEngineConfigurer() {
return engineConfiguration -> {
engineConfiguration.setIdentityQueryService(new YourCustomIdentityService());
};
}
info

Do not define your custom identity query service as a bean. The DesignIdentityQueryService is exposed as a bean once the Design engine is created.

For more details about writing your own DesignIdentityQueryService have a look at Custom Identity Query Service.

Remember Me Services

Flowable provides stateless applications: no information is stored in any of the servers running them. However, sometimes, it is important to store some data to increase the end-user happiness. A good example is user tokens, which are stored by the RememberMe Services in the database so that users do not have to log in again in the case a server is down. The default RememberMe Services of Flowable enrich the tokens with useful information, such as User-Agent and IP addresses, enabling further possibilities for special auditing use cases.

LDAP

This section will explain how to configure your custom Flowable Design application to work with LDAP. For all the configuration properties have a look at the LDAP Installation Guide.

In order to work with LDAP in your application you need to add the following dependencies (on top of the default flowable ones):

<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</dependency>


<!-- LDAP Security needed only if projects need to use the LDAP Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>

Then in your SecurityConfiguration you'll need to do the following:

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class SecurityConfiguration {

@Bean
public DesignLdapAuthenticationProviderFactory ldapAuthenticationProvider() {
return new DesignLdapAuthenticationProviderFactory();
}

@Bean
@Order(10)
public SecurityFilterChain ldapDefaultSecurity(HttpSecurity http, ObjectProvider<DesignHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
for (DesignHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream().toList()) {
customizer.customize(http);
}
// ...

return http.build();
}
}

Using the DesignLdapAuthenticationProviderFactory will make sure that the configuration properties are properly applied for authentication security. If you need to customize something, you use the DesignLdapAuthenticationProviderFactory and customize it.

OAuth2

This section will explain how to configure your custom Flowable application to work with OAuth2. For all the configuration properties have a look at the OAuth2 Installation Guide.

Depending on how you want to use OAuth2 for your application you will need to add some or all of the following dependencies (on top of the default flowable ones):

  • spring-boot-starter-oauth2-client - When the users will log in to the application by being redirected to the SSO.
  • spring-boot-starter-oauth2-resource-server - When the application is accessed using already authenticated users / machines. i.e. machine to machine communication, Flowable Control to Work, Flowable Design to Work, etc.
 <!-- OAuth2 Client needed only if projects needs to use OAuth2 Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!--
OAuth2 Resource Server needed only if projects need to use OAuth2 Security to communicate with the Flowable REST API.
e.g. machine to machine, Flowable Control to Work, Flowable Design to Work
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

Then in your SecurityConfiguration you'll need to do the following:

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class SecurityConfiguration {

@Bean
@Order(10)
public SecurityFilterChain oauthDefaultSecurity(HttpSecurity http, ObjectProvider<DesignHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
for (DesignHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream().toList()) {
customizer.customize(http);
}

http.exceptionHandling(exceptionHandling -> exceptionHandling
// Using this entry point as a default entry point if none of the others match (the first default entry point is the default entry point).
// This one will not match any request, so it won't override other defaults from Spring Security
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), RequestMatchers.not(AnyRequestMatcher.INSTANCE))
// using this no op authentication entry point, due to Spring Boot (3.x) no longer using a default error handling
.defaultAuthenticationEntryPointFor((request, response, authException) -> {}, new DispatcherTypeRequestMatcher(DispatcherType.ERROR))
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"))
);

// We mark all requests as authenticated in order for the redirect to happen when the application is accessed
// We allow the favicon to always be available, since it is invoked by the browser itself
http.authorizeHttpRequests(configurer -> configurer
.requestMatchers(antMatcher("/favicon.ico")).permitAll()
.anyRequest().authenticated()
);
// Currently an HttpSessionSecurityContextRepository is needed for the oauth2 to work
HttpSessionSecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
securityContextRepository.setDisableUrlRewriting(true);
http.securityContext(securityContext -> securityContext.securityContextRepository(securityContextRepository));

http.addFilterAfter(new MatchingRequestParameterNameRemovalRedirectFilter(), RequestCacheAwareFilter.class);

http.oauth2Login(Customizer.withDefaults());
http.oauth2Client(Customizer.withDefaults());

ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
JwtDecoder jwtDecoder = applicationContext
.getBeanProvider(JwtDecoder.class)
.getIfAvailable();
if (jwtDecoder != null) {
// Doing this since Flowable Access token authentication could be enabled through properties.
// if you need to use Bearer authentication, and Flowable Access token then you should register the jwt configurer only if the flowable one isn't already configured
if (http.getConfigurer(FlowableDesignJwtResourceServerConfigurer.class) == null) {
http.oauth2ResourceServer(configurer -> configurer.jwt(Customizer.withDefaults()));
}
}

return http.build();
}
}

Using the FlowableDesignJwtResourceServerConfigurer will make sure that the configuration properties are applied for authentication security. The Flowable Design Security auto configurations will make sure that the required mapping properties are applied.

Refresh Token

v3.16.6+

When using OAuth2 the offline_access scope can be used for the client registration. This will enable the Flowable Platform Application to refresh the access token when it expires. e.g. when communicating between Design and Work using the currently authenticated user access token.

When using the out-of-the-box application the property application.design.security.oauth2.authorized-client-repository-type with a default value of session can be used. With this the refresh token is stored in the HTTP session and can be used to refresh the access token when it expires.

When creating a custom application then the OAuth2AuthorizedClientRepository should be exposed. This repository should be able to store the refresh token and be able to retrieve it when needed. By default Spring Security is using an in memory implementation. This can be changed to use an HTTP Session implementation by using the following configuration:

@Bean
public OAuth2AuthorizedClientRepository customOauth2AuthorizedClientRepository() {
return new HttpSessionOAuth2AuthorizedClientRepository();
}

Custom Security Authentication

Flowable relies on the Spring Security framework to provide authentication and access control capabilities. Spring Security is an open source library that provides connectors and adapters to many security standards such as OAuth, SAML, basic authentication, etc. As with all Spring frameworks, it is designed to be highly customizable and adaptable to many security requirements.

This document describes how you can create your own Custom Authentication From Scratch and Custom Authentication With Existing Integration. If you need to create a custom identity query service then have a look at Custom Identity Query Service. We assume that you are already familiar with the basics defined in Security and Identity Management.

Custom Authentication From Scratch

For this example, we assume that you have some kind of Single Sign-On (SSO) system which performs the authorization for the users and attaches the userId, tenantId and groups as HTTP Headers on the incoming requests. Additionally, you don’t have some common libraries in your organization that integrate with Spring Security so you would need to do everything on your own.

These are the headers which are added by your SSO:

Incoming Headers.

X-USER-ID: kermit
X-TENANT-ID: muppets
X-GROUP: Accounting
X-GROUP: IT
X-GROUP: HR

We now need to intercept the request and extract this information into a so-called Authentication. Spring Security already has us covered we need to extend the AbstractPreAuthenticatedProcessingFilter and add our extraction logic.

We will create our own implementation of the principal that would store the information.

CustomPrincipal.

public class CustomPrincipal implements Principal {

protected String userId;
protected String tenantId;
protected Collection<String> groups;

public CustomPrincipal(String userId, String tenantId, Collection<String> groups) {
this.userId = userId;
this.tenantId = tenantId;
this.groups = groups;
}

@Override
public String getName() {
return userId;
}

// getters omitted
}

We are now going to write the authentication filter, which uses the HttpServletRequest to extract the information from the headers. Extending from AbstractPreAuthenticatedProcessingFilter allows us to easily hook in with the Spring Security authentication. It is going to intercept all requests and perform the authentication via the AuthenticationManager.

CustomHeaderAuthenticationFilter.

public class CustomHeaderAuthenticationFilter
extends AbstractPreAuthenticatedProcessingFilter {

protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String userId = request.getHeader("X-USER-ID");
String tenantId = request.getHeader("X-TENANT-ID");
Enumeration<String> headersEnumeration = request.getHeaders("X-GROUP-ID");
Set<String> groups = new HashSet<>();
while (headersEnumeration.hasMoreElements()) {
groups.add(headersEnumeration.nextElement());
}
return new CustomPrincipal(userId, tenantId, groups);
}
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// The request is already authenticated there is nothing we need to do
return "N/A";
}
}

The next step is to write a custom AuthenticationUserDetailsService that loads the Spring Security UserDetails from the PreAuthenticationToken created by our CustomHeaderAuthenticationFilter.

CustomAuthenticationUserDetailsService.

public class CustomAuthenticationUserDetailsService
implements AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {

@Override
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) {
CustomPrincipal customPrincipal = (CustomPrincipal) token.getPrincipal();
Collection<? extends GrantedAuthority> authorities = new ArrayList<>();
// have some custom logic to get more authorities
return User.withUsername(customPrincipal.getUserId())
.password("n/A")
.authorities(authorities)
.build();
}

}

We now have all the needed pieces to make Spring Security work with our authentication. We are now going to add a custom DesignSecurityScopeProvider and DesignSecurityScope so that we can link our custom security with Flowable.

CustomSecurityScope.

public class DesignCustomSecurityScope implements DesignSecurityScope {

protected Authentication authentication;

public CustomSecurityScope(Authentication authentication) {
this.authentication = authentication;
}

@Override
public String getUserId() {
return authentication.getName();
}

@Override
public Set<String> getGroupKeys() {
return new HashSet<>(getCustomPrincipal().getGroups());
}

@Override
public String getTenantId() {
return getCustomPrincipal().getTenantId();
}

@Override
public boolean hasAdminRights() {
return hasAuthority("access-admin")
}

@Override
public boolean hasAuthority(String authority) {
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
if (Objects.equals(authority, grantedAuthority.getAuthority())) {
return true;
}
}

return false;
}

protected CustomPrincipal getCustomPrincipal() {
return (CustomPrincipal) authentication.getPrincipal();
}
}

CustomSecurityScopeProvider.

public class CustomSecurityProvider implements DesignSecurityScopeProvider {

@Override
public DesignSecurityScope getSecurityScope(Principal principal) {
if (principal instanceof Authentication) {
return new CustomSecurityScope((Authentication) principal);
}
return new FlowablePrincipalSecurityScope(principal);
}
}

We now also have all the pieces to make the Security work with Flowable. The final step is to actually link all of this together. To make the linking a bit easier we are going to add a CustomHttpConfigurer.

public class CustomHttpConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<CustomHttpConfigurer<H>, H> {

@Override
public void init(H http) {
PreAuthenticatedAuthenticationProvider authenticationProvider
= new PreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(
new CustomAuthenticationUserDetailsService()
);
authenticationProvider = postProcess(authenticationProvider);

http
.authenticationProvider(authenticationProvider)
.setSharedObject(
AuthenticationEntryPoint.class,
new Http403ForbiddenEntryPoint()
);
}

@Override
public void configure(H http) {
AuthenticationManager authenticationManager
= http.getSharedObject(AuthenticationManager.class);

CustomHeaderAuthenticationFilter authenticationFilter
= new CustomHeaderAuthenticationFilter();
authenticationFilter.setAuthenticationManager(authenticationManager);
authenticationFilter = postProcess(authenticationFilter);

http.addFilter(authenticationFilter);
}
}
  • Register our CustomAuthenticationUserDetailsService with the out of the box PreAuthenticatedAuthenticationProvider

  • Register the authentication provider with Spring Security

  • Since all our requests are authenticated via our SSO we send back HTTP 403 if there was no authentication

  • Create and add our CustomHeaderAuthenticationFilter

Finally, we need to register this as a Spring Configuration. For this we are going to adapt the out of the box security configuration for Flowable so our custom configuration works.

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

@Bean
@Order(10)
public SecurityFilterChain customDefaultSecurity(HttpSecurity http, ObjectProvider<DesignHttpSecurityCustomizer> httpSecurityCustomizers) throws Exception {
for (DesignHttpSecurityCustomizer customizer : httpSecurityCustomizers.orderedStream().toList()) {
customizer.customize(http);
}

http
.apply(new CustomHttpConfigurer<>())
.and()
.authorizeHttpRequests(configurer -> configurer
// allow context root for all (it triggers the loading of the initial page)
.requestMatchers(antMatcher("/")).permitAll()
.requestMatchers(
antMatcher("/**/*.svg"), antMatcher("/**/*.ico"), antMatcher("/**/*.png"), antMatcher("/**/*.woff2"), antMatcher("/**/*.css"),
antMatcher("/**/*.woff"), antMatcher("/**/*.html"), antMatcher("/**/*.js"),
antMatcher("/**/flowable-frontend-configuration"),
antMatcher("/**/index.html")).permitAll()
.anyRequest().authenticated()
);

return http.build();
}

@Bean
public CustomSecurityProvider() {
return new CustomSecurityProvider();
}
}
  • Apply our CustomConfigurer to the Spring Security configuration

  • Register our CustomSecurityProvider as a Spring Bean

Custom Authentication With Existing Integration

In the previous section we assumed that your organization does not have any Spring Security Integration. However, in case that your organization already has some kind of integration then the only thing you need to do is do provide a custom DesignSecurityScopeProvider and DesignSecurityScope that would perform the mapping from your Organization Spring Security integration to Flowable.

For the purposes of this example lets imagine that the following stands:

  • You have a custom implementation of the Spring Security UserDetails named AcmeUserDetails. It contains the tenantId for your user.

  • Your groups are mapped as a custom GroupGrantedAuthority and that you have other GrantedAuthority(ies) which represent something else for your organization.

For this we will have the following implementations

AcmeSecurityScope.

public class AcmeSecurityScope implements DesignSecurityScope {

protected Authentication authentication;

public AcmeSecurityScope(Authentication authentication) {
this.authentication = authentication;
}

@Override
public String getUserId() {
return authentication.getName();
}

@Override
public Set<String> getGroupKeys() {
Set<String> groupKeys = new HashSet<>();
for (GrantedAuthority authority: authentication.getGrantedAuthorities()) {
if (authority instanceof GroupGrantedAuthority) {
groupKeys.add(((GroupGrantedAuthority) authority).getGroupId());
}
}
return groupKeys;
}

@Override
public String getTenantId() {
return getAcmeUserDetails().getTenantId();
}

@Override
public boolean hasAdminRights() {
return hasAuthority("access-admin")
}

@Override
public boolean hasAuthority(String authority) {
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
if (Objects.equals(authority, grantedAuthority.getAuthority())) {
return true;
}
}

return false;
}

protected AcmeUserDetails getAcmeUserDetails() {
return (AcmeUserDetails) authentication.getDetails();
}
}

AcmeSecurityScopeProvider.

public class AcmeSecurityProvider implements DesignSecurityScopeProvider {

@Override
public DesignSecurityScope getSecurityScope(Principal principal) {
if (principal instanceof Authentication) {
return new AcmeSecurityScope((Authentication) principal);
}
return new FlowablePrincipalSecurityScope(principal);
}
}

Finally, we need to register this as a Spring Configuration. You would need to consult with your organization documentation for how to configure your security configuration. The only thing needed for the integration with Flowable to work is to have the AcmeSecurityScopeProvider registered as a Spring Bean

@Configuration
public class AcmeFlowableConfiguration {

@Bean
public AcmeSecurityProvider() {
return new AcmeSecurityProvider();
}
}

Custom Identity Query Service

This document describes how you can create your own Custom DesignIdentityQueryService and thus avoid syncing your users into the Flowable database. We assume that you are already familiar with the basics defined in Security and Identity Management and Overriding the Default Identity Service. If you need to create a custom authentication then have a look at Custom Security Authentication.

For this example, we are going to assume that your users are going to be retrieved from LDAP and we are going to use Spring LDAP to do the querying.

:::node Flowable already provides out-of-the-box support for LDAP, but this is still only an example :::

AcmeDesignIdentityQueryService.

public class AcmeDesignIdentityQueryService implements DesignIdentityQueryService {

protected LdapTemplate ldapTemplate;

public AcmeDesignIdentityQueryService(LdapTemplate ldapTemplate) {
super(idmEngineConfiguration);
this.ldapTemplate = ldapTemplate;
}

// Retrieve methods

@Override
public DesignUserQuery createUserQuery() {
return new AcmeUserQuery(ldapTemplate);
}

@Override
public Collection<String> findGroupsForUser(String userId) {
return Collections.emptyList();
}

}

For the purposes of this example we are going to show the implementation of only of subsets of methods of the AcmeUserQuery.

AcmePlatformUserQuery.

public class AcmeUserQuery
extends AbstractQuery<DesignUserQuery, DesignUser>
implements DesignUserQuery {

protected LdapTemplate ldapTemplate;

protected String userId;
protected String searchText;

public AcmePlatformUserQuery(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}

@Override
public DesignUserQuery id(String id) {
userId = id;
return this;
}

@Override
public DesignUserQuery searchText(String searchText) {
this.searchText = searchText;
return this;
}

// ...

@Override
public long executeCount(CommandContext commandContext) {
return executeList(commandContext).size();
}

@Override
public List<PlatformUser> executeList(CommandContext commandContext) {
LdapQueryBuilder queryBuilder = LdapQueryBuilder.query();

ContainerCriteria criteria = queryBuilder.where("objectClass").is("user");
if (userId != null) {
criteria = criteria.and("sAMAccountName").is(userId);
}

if (searchText != null) {
criteria = criteria.and("displayName").like(displayNameLike.replace('%', '*'));
}

return ldapTemplate.search(criteria, new AttributesMapper<DesignUser>() {

@Override
public PlatformUser mapFromAttributes(Attributes attributes) throws NamingException {
MyDesignUser user = new MyDesignUser();

user.setId(getAttribute("sAMAccountName", attributes));
user.setFirstName(getAttribute("givenName", attributes));
user.setLastName(getAttribute("sn", attributes));
user.setDisplayName(getAttribute("displayName", attributes));
user.setEmail(getAttribute("mail", attributes));
user.setTenantId(getAttribute("tenant", attributes));

return user;
}
});
}

protected static String getAttribute(String attributeName, Attributes attributes) {
Attribute attribute = attributes.get(attributeName);
if (attribute != null && attribute.size() > 0) {
try {
return (String) attribute.get();
} catch (NamingException e) {
throw new FlowableException("Exception getting attribute " + attributeName);
}
} else {
return null;
}
}

protected static class MyDesignUser implements DesignUser {
protected String id;
protected String tenantId;
protected String password;
protected String firstName;
protected String lastName;
protected String displayName;
protected String email;

// getters and setters omitted for brevity

}
}

We now need to register our AcmeDesignIdentityQueryService with the Design Engine. This is done by using the Consumer as follows:

Register AcmeDesignIdentityQueryService with the Design Engine.

@Configuration
public class AcmeFlowableConfiguration {

@Bean
public Consumer<DesignEngineConfiguration> acmeDesignEngineConfigurer(LdapTemplate ldapTemplate) {
return engineConfiguration -> {
engineConfiguration.setIdentityQueryService(new AcmeDesignIdentityQueryService(ldapTemplate));
};
}
}