Custom Endpoints

Requests to the Jmix application are protected by the Spring Security framework. This section explains how you can configure access to custom API endpoints.

General Information

To gain a deep understanding of how endpoint security works, read the relevant sections of the Spring Security documentation:

Spring Security uses special SecurityFilterChain beans to determine which URLs should be protected. Each SecurityFilterChain bean is configured by the HttpSecurity builder. An application can have multiple SecurityFilterChain beans declared. In this case, it is extremely important to indicate their correct order. See the Multiple HttpSecurity Instances section of the Spring Security documentation for how to configure multiple HttpSecurity objects.

Each Jmix application by default contains a security configuration that extends the VaadinWebSecurity class. This configuration sets up access to internal Vaadin endpoints and delegates all requests authorization to Jmix and Vaadin mechanisms (grants access to views using view controller annotation or by analyzing user’s resource roles). SecurityFilterChain created by this configuration always has the lowest priority and is always invoked last.

Add-ons like OpenID Connect or Authorization Server bring their own SecurityFilterChain beans that protect authorization or resource server endpoints. These beans are always invoked before the one from the UI module.

Custom Endpoints Security

To define custom security rules for endpoints, declare a new SecurityFilterChain bean. It is important that the order of this bean must be less than the order of SecurityFilterChain beans provided by Jmix framework.

You can find order value constants used by Jmix in the JmixSecurityFilterChainOrder interface. The rule of thumb is to use the JmixSecurityFilterChainOrder.CUSTOM, JmixSecurityFilterChainOrder.CUSTOM - 10 and similar values for your security filter chains.

A simple SecurityFilterChain bean definition may look as follows:

@Bean
@Order(JmixSecurityFilterChainOrder.CUSTOM)
SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
    http.securityMatcher("/public/**")
            .authorizeHttpRequests(authorize ->
                    authorize.anyRequest().permitAll()
            );
    return http.build();
}

The configuration above grants access to all requests for endpoints that match the /public/** pattern.

Examples

Public Endpoints

Let’s assume that you have a controller with two methods, and you want these methods to be available for any user without authentication.

@RestController
public class GreetingController {

    @GetMapping("/greeting/hello")
    public String hello() {
        return "Hello!";
    }

    @PostMapping("/greeting/hi")
    public String hi() {
        return "Hi!";
    }
}

Public access can be configured using the following configuration:

@Configuration
public class AnonymousControllerSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.CUSTOM) (1)
    SecurityFilterChain greetingFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/greeting/**") (2)
                .authorizeHttpRequests(authorize ->
                        authorize.anyRequest().permitAll() (3)
                )
                .csrf(csrf -> csrf.disable()); (4)
        JmixHttpSecurityUtils.configureAnonymous(http); (5)
        return http.build();
    }
}
1 JmixSecurityFilterChainOrder.CUSTOM order is less than any other Jmix security filter chain order, so your filter chain will be used before any filter chain from Jmix.
2 securityMatcher() is used to determine whether a given HttpSecurity should be applied to the request. The request matcher from the example will match requests with URLs that follow the pattern /greeting/**. Requests for other URLs will be processed by a default security filter chain from Jmix UI module.
3 permitAll() instruction grants access to endpoints.
4 Disables CSRF for POST requests.
5 The invocation of JmixHttpSecurityUtils.configureAnonymous(http) configures anonymous authentication by setting the anonymous user returned by the Jmix UserRepository to the security context.

HTTP Basic Authentication

The example demonstrates how to protect controller endpoints using HTTP Basic authentication.

The controller class:

@RestController
@RequestMapping("/api")
public class BasicGreetingController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello!";
    }

    @GetMapping("/public/hi")
    public String publicHi() {
        return "Hi!";
    }
}

Requests with URLs matching the /api/** pattern should be protected with HTTP Basic authentication, while requests to /api/public/** should be available to all users. This can be achieved by using the configuration below:

@Configuration
public class BasicControllerSecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.CUSTOM) (1)
    SecurityFilterChain basicControllerFilterChain(
            HttpSecurity http,
            AuthenticationManager authenticationManager) throws Exception {
        http.securityMatcher("/api/**") (2)
                .authorizeHttpRequests(requests ->
                        requests
                                .requestMatchers("/api/public/**").permitAll() (3)
                                .anyRequest().authenticated() (4)
                )
                .httpBasic(Customizer.withDefaults()) (5)
                .authenticationManager(authenticationManager); (6)
        return http.build();
    }
}
1 JmixSecurityFilterChainOrder.CUSTOM order is less than any other Jmix security filter chain order, so your filter chain will be used before any filter chain from Jmix.
2 Security matcher indicates that the HttpSecurity will only be applied for /api/** requests.
3 If the request is selected by security matcher then we can provide further rules. All requests for /api/public/** must be permitted without authentication.
4 All requests that don’t match the /api/public/** pattern defined above must be authenticated.
5 Enable basic authentication.
6 Use the AuthenticationManager configured by Jmix for basic authentication.

Requests to /api/** endpoints must contain a header in the form of Authorization: Basic <credentials>, where <credentials> is the Base64 encoding of username and password joined by a single colon. For example:

GET /api/hello HTTP/1.1
Host: server.example.com
Authorization: Basic YWRtaW46YWRtaW4=
In this example, public access to /api/public/** could be alternatively configured using another SecurityFilterChain bean that has securityMatcher("/api/public/**") and an order less than the current one, e.g. JmixSecurityFilterChainOrder.CUSTOM - 10.

Token Based Authentication

You can protect custom endpoints with tokens issued by the Authorization Server. The Authorization Server add-on security configuration provides extension points that can be used for that purpose.

Suppose that you have the following REST controller:

@RestController
public class GreetingController {

    @GetMapping("/greeting/hello")
    public String hello() {
        return "Hello!";
    }

    @PostMapping("/greeting/hi")
    public String hi() {
        return "Hi!";
    }
}

To make /greeting/** endpoints protected with the access token you should define a bean that implements the io.jmix.core.security.AuthorizedUrlsProvider interface and return a list of URL patterns from its getAuthenticatedUrlPatterns() method:

@Component
public class GreetingAuthorizedUrlsProvider implements AuthorizedUrlsProvider {

    @Override
    public Collection<String> getAuthenticatedUrlPatterns() {
        return List.of("/greeting/**");
    }

    @Override
    public Collection<String> getAnonymousUrlPatterns() {
        return List.of();
    }
}

After the above configuration is defined, all requests to /greeting/** endpoints will need an access token in the Authorization header. For example:

GET /greeting/hello HTTP/1.1
Host: server.example.com
Authorization: Bearer <ACCESS_TOKEN>

Troubleshooting

If you come across a 401 Unauthorized or 403 Forbidden HTTP error or have any other issues related to endpoint security, then it’s very likely that Spring Security logging will help you to understand what is going on.

To enable the logging add the following application property with DEBUG or TRACE value to the application.properties file:

logging.level.org.springframework.security = TRACE