jmix-framework / jmix

Jmix framework
https://www.jmix.io
Apache License 2.0
562 stars 121 forks source link

Remove bpm-rest-starter #2944

Open gorbunkov opened 7 months ago

gorbunkov commented 7 months ago

There is a io.jmix.bpm:jmix-bpm-rest-starter in the repository. This starter attaches standard Flowable REST API endpoints to the project. We need to analyze:

An example of security configuration that may be defined in the project:

@Configuration
public class BpmRestSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.AUTHSERVER_RESOURCE_SERVER - 10)
    SecurityFilterChain bpmRestSecurityFilterChain(HttpSecurity http,
                                              AuthenticationManager authenticationManager
                                              ) throws Exception {
        http.securityMatcher(new AntPathRequestMatcher("/rest/bpm/**"))
                .httpBasic(Customizer.withDefaults())
                .authenticationManager(authenticationManager)
                .authorizeHttpRequests(requests -> requests.anyRequest().authenticated());

        return http.build();
    }
}
gorbunkov commented 6 months ago

The following configuration may be added to the project (as a temporary workaround) when OIDC add-on is used. It will secure BPM REST endpoints by tokens issued by identity provider (e.g. keycloak).

@Configuration
public class BpmRestSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.OIDC_RESOURCE_SERVER - 10)
    public SecurityFilterChain myBpmRestSecurityFilterChain(HttpSecurity http,
                                                   JmixJwtAuthenticationConverter jmixJwtAuthenticationConverter,
                                                   ApplicationEventPublisher applicationEventPublisher) throws Exception {
        http
                .securityMatcher(new AntPathRequestMatcher("/rest/bpm/**"))
                .oauth2ResourceServer(resourceServer -> {
                    resourceServer.jwt(jwt -> {
                        jwt.jwtAuthenticationConverter(jmixJwtAuthenticationConverter);
                    });
                })
                .cors(Customizer.withDefaults());

        //this filter is required if you want to check whether the user has the "bpm.rest.enabled" specific policy
        OidcResourceServerEventSecurityFilter resourceServerEventSecurityFilter =
                new OidcResourceServerEventSecurityFilter(applicationEventPublisher);
        http.addFilterBefore(resourceServerEventSecurityFilter, AuthorizationFilter.class);
        return http.build();
    }
}
gorbunkov commented 4 months ago

Defining the following bean will protect BPM REST API endpoints with the token issued by Authorization Server add-on:

import io.jmix.autoconfigure.authserver.AuthServerAutoConfiguration;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

@Component
@Qualifier(AuthServerAutoConfiguration.ResourceServerSecurityConfiguration.SECURITY_CONFIGURER_QUALIFIER)
public class BpmRestSecurityConfigurer extends AbstractHttpConfigurer<BpmRestSecurityConfigurer, HttpSecurity> {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.securityMatcher(new AntPathRequestMatcher("/rest/bpm/**"))
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated());
    }
}
NikitaShchienko commented 1 month ago

Update

Currently, the BPM REST implementation adds a "Flowable" REST dependency, one controller, and a security configuration. Some projects do not require the REST API for BPM. You can add the entire configuration to the project yourself.

You need to do the following:

m-orlova commented 1 month ago

We can use Flowable REST API to interact with a Jmix application that uses the BPM add-on.

1 Flowable REST dependencies

  1. BPMN API provides endpoints to work with process definitions, process instances (runtime and history), user tasks etc. Full list of endpoints is described in Flowable docs. To add these endpoints it is required to add the following dependency to build.gradle in Jmix project that uses the BPM add-on:

      implementation 'org.flowable:flowable-spring-boot-starter-process-rest'
  2. DMN API provides endpoints to work with DMN tables. Full list of endpoints is described in Flowable docs. To add these endpoints it is required to add the following dependency to build.gradle in Jmix project that uses the BPM add-on:

      implementation 'org.flowable:flowable-spring-boot-starter-dmn-rest'

    2 Base path configuration

    Flowable endpoints are available at the base path. By default,

  3. BPMN API is available at /process-api. Example of full URL: http://localhost:8080/process-api/repository/process-definitions.

  4. DMN API is availbale at /dmn-api. Example of full URL: http://localhost:8080/dmn-api/dmn-repository/deployments

It is possible to change these values using application properties. For BPMN API: set flowable.process.servlet.path in application.properties. For example:

flowable.process.servlet.path=/rest/bpm/process

Example of result URL:

http://localhost:8080/rest/bpm/process/repository/process-definitions

For DMN API: set flowable.dmn.servlet.path in application.properties. For example:

flowable.process.servlet.path=/rest/bpm/dmn

Example of result URL:

http://localhost:8080/rest/bpm/dmn/dmn-repository/deployments

Note: If a context path (server.servlet.context-path application property) is set for Jmix application, not to forget to add it to the result URL before Flowable REST base path. E.g., if server.servlet.context-path=/app, then the result URL for example above will be http://localhost:8080/app/rest/bpm/dmn/dmn-repository/deployments.

3 Security configuration for Flowable REST

Once the dependencies are added, it is required to configure a Spring security for Flowable REST endpoints in the Jmix project they are added to.

Note: The Spring security configuration examples below are described for the default base path values ​​used for Flowable REST (/process-api and /dmn-api). If these values ​​are changed as described in the previous section, be sure to also change the endpoint in the request matchers in the security configuration.

3.1 Public access

If it is required to give a public access to Flowable REST endpoints, use the following security configuration:

@Configuration
public class BpmRestSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.CUSTOM)
    SecurityFilterChain bpmRestFilterChain(HttpSecurity http) throws Exception {
        AntPathRequestMatcher processRestMatcher = antMatcher("/process-api/**"); // Matcher to filter requests related to Flowable BPMN API
        AntPathRequestMatcher dmnRestMatcher = antMatcher("/dmn-api/**"); // Matcher to filter requests related to Flowable DMN API

        http.securityMatcher(RequestMatchers.anyOf(processRestMatcher, dmnRestMatcher)) // Applies this SecurityFilterChain to BPMN and DMN API endpoints only
                .csrf(AbstractHttpConfigurer::disable) // Disable CSRF for POST requests
                .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()); // Gives public access to any endpoint
         JmixHttpSecurityUtils.configureAnonymous(http); //Use anonymous user returned by the Jmix UserRepository
        return http.build();
    }
}

3.2 Basic authentication

Access for authenticated users This example of Spring security configuration describes that basic authentication is configured for /process-api/** and /dmn-api/** endpoints. User credentials are verified using configured AuthenticationManager. Tip: Jmix provides CoreSecurityConfiguration that contains this bean initialization out-of-box.

@Configuration
public class BpmRestSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.CUSTOM)
    SecurityFilterChain bpmRestSecurityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
        AntPathRequestMatcher processRestMatcher = antMatcher("/process-api/**"); 
        AntPathRequestMatcher dmnRestMatcher = antMatcher("/dmn-api/**"); 
        http.securityMatcher(RequestMatchers.anyOf(processRestMatcher, dmnRestMatcher))
                .httpBasic(Customizer.withDefaults()) //Use Basic authentication
                .authenticationManager(authenticationManager) //Use configured `authenticatedManager` to authenticate users
                .csrf(AbstractHttpConfigurer::disable) //Disables CSRF for POST requests
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()); //Gives access to any endpoint to any authenticated user

        return http.build();
    }
}

3.3 Authentication with Authorization Server add-on

These examples describe how to configure a security if the Authorization Server add-on is used.

Access for authenticated users This example of configuration describes that /process-api/** and /dmn-api/** endpoints should be available for authenticated users.

@Configuration
public class BpmRestSecurityConfiguration {

   @Bean
   AuthenticatedUrlPatternsProvider bpmRestEndpointsProvider() {
        return () -> List.of("/process-api/**", "/dmn-api/**");
    }
}

Access for users with specific role

This example of configuration describes that /process-api/** and /dmn-api/** endpoints should be available for authenticated users with a custom resource role.

Jmix resource role example:

@ResourceRole(name = "BpmRestApiRole", code = BpmRestApiRole.CODE, scope = "API")
public interface BpmRestApiRole {
    String CODE = "bpm-rest-api-role";

}

Configuration example:

@Configuration
public class BpmRestSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.CUSTOM)
    SecurityFilterChain bpmRestFilterChain(HttpSecurity http, OpaqueTokenIntrospector opaqueTokenIntrospector) throws Exception {
        AntPathRequestMatcher processRestMatcher = antMatcher("/process-api/**");
        AntPathRequestMatcher dmnRestMatcher = antMatcher("/dmn-api/**"); 
        http.securityMatcher(RequestMatchers.anyOf(processRestMatcher, dmnRestMatcher)) // Apply this SecurityFilterChain to BPMN and DPM API endpoints only
                .authorizeHttpRequests(authorize -> authorize.anyRequest().hasRole(BpmRestApiRole.CODE)) //Gives access to endpoints to users with specified role
                .oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(opaqueToken -> opaqueToken.introspector(opaqueTokenIntrospector)))
                .cors(Customizer.withDefaults());

        return http.build();
    }
}

3.4 Authentication with OpenID Connect add-on

These examples describe how to configure a security if the OpenID Connect add-on is used.

Access for authenticated users This example of configuration describes that /process-api/** and /dmn-api/** endpoints should be available for authenticated users.

@Configuration
public class BpmRestSecurityConfiguration {

   @Bean
   AuthenticatedUrlPatternsProvider bpmRestEndpointsProvider() {
        return () -> List.of("/process-api/**", "/dmn-api/**");
    }
}

Access to specific endpoint for users with a specific role This example of configuration describes that /process-api/** and /dmn-api/** endpoints should be available for authenticated users and /process-api/history/** endpoints should be available for users with a custom resource role.

Jmix resource role:

@ResourceRole(name = "HistoryAdminRole", code = HistoryAdminRole.CODE)
public interface HistoryAdminRole {
    String CODE = "history-admin-role";
}

Configuration example:


@Configuration
public class BpmRestSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.CUSTOM)
    public SecurityFilterChain bpmRestFilterChain(HttpSecurity http,
                                                  JmixJwtAuthenticationConverter jmixJwtAuthenticationConverter) throws Exception {
        AntPathRequestMatcher processRestMatcher = antMatcher("/process-api/**");
        AntPathRequestMatcher dmnRestMatcher = antMatcher("/dmn-api/**"); 
        http.securityMatcher(RequestMatchers.anyOf(processRestMatcher, dmnRestMatcher))
                .oauth2ResourceServer(resourceServer -> resourceServer.jwt(jwt -> jwt.jwtAuthenticationConverter(jmixJwtAuthenticationConverter))) 
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(antMatcher("/history/**")).hasRole(HistoryAdminRole.CODE)
                        .anyRequest().authenticated())
                .cors(Customizer.withDefaults());

        return http.build();
    }

}