Add Rest api endpoints to vaadin app

Hi.
I want to include a Rest API into my Vaadin application based on Spring.
The vaadin App should be on /* and the API on /api/*.

Is that possible?

Yes, that should be possible.

Olli Tietäväinen:
Yes, that should be possible.

How can i do that? @RestController’s get completely ignored.

Somebody else got it working here: https://vaadin.com/forum/thread/17817591/springboot-restcontroller-in-vaadin-14

Olli Tietäväinen:
Somebody else got it working here: https://vaadin.com/forum/thread/17817591/springboot-restcontroller-in-vaadin-14

I already tried that, but vaadin automatically uses the endpoints.

I’m not sure what you mean; I have a Vaadin 14 application that uses a @RestController for api endpoints, it’s working like a charm. My request-mapping starts with “/api/” too. The problem probably lies within your own implementation and not with vaadin.
Can you post your controller class?

Kaspar Scherrer:
I’m not sure what you mean; I have a Vaadin 14 application that uses a @RestController for api endpoints, it’s working like a charm. My request-mapping starts with “/api/” too. The problem probably lies within your own implementation and not with vaadin.
Can you post your controller class?

How do you manage the request mapping?
And what do you mean with “controller class”?

package package;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("api")
public class SimpleRestController {

  @GetMapping(value = "/test", produces = "application/json")
  public String test() {
    return "Test worked";
  }
}

I tried this, but it is catched by vaadin´s path not found exception

With controller class I meant your class that has the controller annotation, the one that you now posted :wink:

Here’s a sample of my working controller:

@RestController
@RequestMapping("/api/foo")
public class FooController {
	@Inject
	private FooRepository fooRepository;
	
	@RequestMapping(value = "/{id}", RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
	@JsonView(JsonViewDefinitions.SingleFooView.class)
	public ResponseEntity<Foo> findFoo(@PathVariable Long id) {
		Optional<Foo> optionalFoo = fooRepository.findById(id);
		if(!optionalFoo.isPresent()){
			throw new IllegalArgumentException("There is no Foo with id "+id);
		} 
		return ResponseEntity.ok(optionalFoo.get());
	}
}

You will see that there aren’t many differences. I use org.springframework.http.ResponseEntity as return types but String should work just okay.
Maybe the leading slash in the @RequestMapping makes it work?

An important thing I had to do was changing the spring security so the web-ui and the api-endpoints are secured in their own way. For the web-ui I need the user to login, while for the api requests I need an Authorization Header sent with every request.
I followed [this baeldung post]
(https://www.baeldung.com/spring-security-multiple-entry-points) to create this WebSecurityConfigurerAdapter-extension:

package myproject.security;

import myproject.security.JWTAuthenticationFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.inject.Inject;

/**
 * - 2 classes that extend WebSecurityConfigurerAdapter to be able to define different security settings for UI and API
 * see https://www.baeldung.com/spring-security-multiple-entry-points
 */
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfiguration {

    /**
     * ONLY FOR /api/**
     * all api requests will have "Authorization" header which I can use to authenticate, if valid. no login needed
     */
    @Configuration
    @Order(1)
    public static class RestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().and().csrf().disable()
                    // make sure to only make rules for /api/**  - everything else will be defined in WebSecurityConfigurationAdapter
                    .antMatcher("/api/**").authorizeRequests().anyRequest().authenticated()

                    // these requests come with an "Authorization" header with a token
                    // Use this token to create an Authentication for this request.
                    .and().addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    }

    /**
     * FOR ANYTHING EXCEPT /api/**
     * for any UI request, redirect to /login if user is not yet authenticated.
     */
    @Configuration
    @Order(2)
    public static class WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().and().csrf().disable()

                    // Register our ExternalHttpSessionRequestCache, that saves unauthorized access attempts, so
                    // the user is redirected after login.
                    .requestCache().requestCache(new ExternalHttpSessionRequestCache())

                    // Restrict access to our application.
                    .and().authorizeRequests()

                    // Allow all flow internal requests
                    .requestMatchers(VaadinSecurityUtils::isFrameworkInternalRequest).permitAll()

                    // Allow all requests by logged in users
                    .anyRequest().authenticated()

                    // login
                    .and()
                    .formLogin()
                    .loginPage(("/login")).permitAll()
                    .loginProcessingUrl("/login")
                    .failureUrl("/login?error")

                    // Register the success handler that redirects users to the page they last tried to access
                    .successHandler(new SavedRequestAwareAuthenticationSuccessHandler())

                    .and().logout().logoutSuccessUrl("/login");
        }

        /**
         * Allows access to static resources, bypassing Spring security.
         */
        @Override
        public void configure(WebSecurity web) {
            web.ignoring().antMatchers(
                    // Push
                    "/VaadiServlet/**",

                    // Vaadin Flow static resources
                    "/VAADIN/**",

                    // the standard favicon URI
                    "/favicon.ico",

                    // the robots exclusion standard
                    "/robots.txt",

                    // web application manifest
                    "/manifest.webmanifest",
                    "/sw.js",
                    "/offline-page.html",

                    // icons and images
                    "/icons/**",
                    "/images/**", 

                    // (development mode) static resources
                    "/frontend/**",

                    // (development mode) webjars
                    "/webjars/**",

                    // (development mode) H2 debugging console
                    "/h2-console/**",

                    // (production mode) static resources
                    "/frontend-es5/**", "/frontend-es6/**");
        }
    }
}

PS: I don’t use the browser to test my api endpoints. I use [postman]
(https://www.getpostman.com/downloads/). Your sentence about vaadins path not found exception lets me think this could be the cause of the problem just as well.

Maybe the leading slash in the @RequestMapping makes it work?

That works, now i get some authentication errors… That should be possible to fix using your configuration class!

Hi,
I have same problem with implementing REST into Vaadin 14 + Springboot. I have tried example above but can’t get the service. Controller looks like this;

@RestController
@RequestMapping("/api/brojObavestenja")
public class BrojObavestenjaController {
	
	private static final String template = "Broj obaveštenja: %s";
	private ObavestenjeOsvimIzvrsenimUplatamaService obavestenjeService;
	//private String name = "proba"

	@RequestMapping(value = "/{name}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
	public String  brojObavestenja(@PathVariable String name) {
		return String.format(template, obavestenjeService.prebroj() + " " + name);
	}
}

And the response is;

Could not navigate to 'api/brojObavestenja'
Reason: Couldn't find route for 'api/brojObavestenja'
Available routes:
    <root>
This detailed message is only shown when running in development mode.

Any idea what am I doing wrong?

yes, you should @Inject / @Autowire your service

Thanks but it didn’t help, this is how I did it:

@RestController
@RequestMapping("/api/brojObavestenja")
public class BrojObavestenjaController {
	
	@Autowired
	private ObavestenjeOsvimIzvrsenimUplatamaService obavestenjeService;
	private static final String template = "Broj obaveštenja: %s";
	//private String name = "proba"

	@RequestMapping(value = "/{name}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
	public String  brojObavestenja(@PathVariable String name) {
		return String.format(template, obavestenjeService.prebroj() + " " + name);
	}
}

When I tried to reach http://localhost:8080/api/brojObavestenja?name=Test the answer from firefox is the same:

Could not navigate to 'api/brojObavestenja'
Reason: Couldn't find route for 'api/brojObavestenja'
Available routes:
    <root>
This detailed message is only shown when running in development mode.

well, you should probably go to the route you mapped to. http://localhost:8080/api/brojObavestenja/Test

you could also check out something like [swagger ui]
(https://swagger.io/tools/swagger-ui/) to have a good overview and testing of your available endpoints

Thank you, I suppose that’s it since I have response in firefox like

SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data

But in raw data I have plain text response as I expected, thanks again!

Kaspar Scherrer:
With controller class I meant your class that has the controller annotation, the one that you now posted :wink:

Here’s a sample of my working controller:

@RestController
@RequestMapping("/api/foo")
public class FooController {
	@Inject
	private FooRepository fooRepository;
	
	@RequestMapping(value = "/{id}", RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
	@JsonView(JsonViewDefinitions.SingleFooView.class)
	public ResponseEntity<Foo> findFoo(@PathVariable Long id) {
		Optional<Foo> optionalFoo = fooRepository.findById(id);
		if(!optionalFoo.isPresent()){
			throw new IllegalArgumentException("There is no Foo with id "+id);
		} 
		return ResponseEntity.ok(optionalFoo.get());
	}
}

You will see that there aren’t many differences. I use org.springframework.http.ResponseEntity as return types but String should work just okay.
Maybe the leading slash in the @RequestMapping makes it work?

An important thing I had to do was changing the spring security so the web-ui and the api-endpoints are secured in their own way. For the web-ui I need the user to login, while for the api requests I need an Authorization Header sent with every request.
I followed [this baeldung post]
(https://www.baeldung.com/spring-security-multiple-entry-points) to create this WebSecurityConfigurerAdapter-extension:

package myproject.security;

import myproject.security.JWTAuthenticationFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.inject.Inject;

/**
 * - 2 classes that extend WebSecurityConfigurerAdapter to be able to define different security settings for UI and API
 * see https://www.baeldung.com/spring-security-multiple-entry-points
 */
@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfiguration {

    /**
     * ONLY FOR /api/**
     * all api requests will have "Authorization" header which I can use to authenticate, if valid. no login needed
     */
    @Configuration
    @Order(1)
    public static class RestSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().and().csrf().disable()
                    // make sure to only make rules for /api/**  - everything else will be defined in WebSecurityConfigurationAdapter
                    .antMatcher("/api/**").authorizeRequests().anyRequest().authenticated()

                    // these requests come with an "Authorization" header with a token
                    // Use this token to create an Authentication for this request.
                    .and().addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        }
    }

    /**
     * FOR ANYTHING EXCEPT /api/**
     * for any UI request, redirect to /login if user is not yet authenticated.
     */
    @Configuration
    @Order(2)
    public static class WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.cors().and().csrf().disable()

                    // Register our ExternalHttpSessionRequestCache, that saves unauthorized access attempts, so
                    // the user is redirected after login.
                    .requestCache().requestCache(new ExternalHttpSessionRequestCache())

                    // Restrict access to our application.
                    .and().authorizeRequests()

                    // Allow all flow internal requests
                    .requestMatchers(VaadinSecurityUtils::isFrameworkInternalRequest).permitAll()

                    // Allow all requests by logged in users
                    .anyRequest().authenticated()

                    // login
                    .and()
                    .formLogin()
                    .loginPage(("/login")).permitAll()
                    .loginProcessingUrl("/login")
                    .failureUrl("/login?error")

                    // Register the success handler that redirects users to the page they last tried to access
                    .successHandler(new SavedRequestAwareAuthenticationSuccessHandler())

                    .and().logout().logoutSuccessUrl("/login");
        }

        /**
         * Allows access to static resources, bypassing Spring security.
         */
        @Override
        public void configure(WebSecurity web) {
            web.ignoring().antMatchers(
                    // Push
                    "/VaadiServlet/**",

                    // Vaadin Flow static resources
                    "/VAADIN/**",

                    // the standard favicon URI
                    "/favicon.ico",

                    // the robots exclusion standard
                    "/robots.txt",

                    // web application manifest
                    "/manifest.webmanifest",
                    "/sw.js",
                    "/offline-page.html",

                    // icons and images
                    "/icons/**",
                    "/images/**", 

                    // (development mode) static resources
                    "/frontend/**",

                    // (development mode) webjars
                    "/webjars/**",

                    // (development mode) H2 debugging console
                    "/h2-console/**",

                    // (production mode) static resources
                    "/frontend-es5/**", "/frontend-es6/**");
        }
    }
}

PS: I don’t use the browser to test my api endpoints. I use [postman]
(https://www.getpostman.com/downloads/). Your sentence about vaadins path not found exception lets me think this could be the cause of the problem just as well.

Friend could you share your file with me JWTAuthenticationFilter please; or the link on github I am following your example to add a service rest to my app vaadin+spring boot y spring security but I don’t understand about the filterJWT

JWTAuthenticationFilter is a custom class of ours that extends GenericFilterBean, with a single method doFilter(..) where the HttpServletRequests Header ‘Authorization’ token is read and the users identity is extracted thereof. Which is then used to create an Authentication object which is set in the SecurityContextHolder from Spring. Lastly, filterChain is continued

public class JWTAuthenticationFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
	    /* TokenAuthenticationService is another custom class of ours, which I will not be able to share. I described above what generally happens in this method. */
        Authentication authentication = TokenAuthenticationService.getInstance().getAuthentication((HttpServletRequest) request);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        filterChain.doFilter(request, response);
    }
}