Setting a Dynamic DataSource in Vaadin Flow Application

Sorry, this is not a pure Flow question. I want to set up multitenancy in my Vaadin application, and obviously a single schema with a tenantID column is easier because it only requires a static DataSource. But I was also looking at the option of separate but identical schema per tenant within a single mySQL database instance for more data security and isolation. So I would need a dynamic DataSource created after a successful user login. I’ve looked at this tutorial https://www.baeldung.com/multitenancy-with-spring-data-jpa, which uses AbstractRoutingDataSource, a Filter to get a HttpServletRequest and then reads a custom http header. However I’m having trouble working out how to set and read a custom http header in the Vaadin Flow application.

My initial approach was to set the custom header in Spring Security configuration, (eg http. headers().addHeaderWriter(new StaticHeadersWriter(“X-TenantID”, “TheUsersSchema”)) and get the user from the LoginView using "Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); but then couldn’t work out how to to populate the http header. I also tried implementing VaadinServiceInitListener and setting header in event.addRequestHandler method without success.

Any help on the best way to achieve this would be appreciated.

I think I have this working now:

  1. In Spring security configuration :
    http.headers().addHeaderWriter(new StaticHeadersWriter("X-TenantID", ""))

  2. Login Form - Before Leave event:

@Override public void beforeLeave(BeforeLeaveEvent beforeLeaveEvent) { // get my schema from schemaService VaadinSession.getCurrent().setAttribute("X-TenantID", <my Schema String>); }

  1. Create TenantContext class as per tutorial:

  2. AbstractRoutingDataSource changed to this (need the try/catch because VaadinSession is initially null when Spring creates dataSource beans):

public class DynamicRouter extends AbstractRoutingDataSource {

` private String schema;

@Override
protected String determineCurrentLookupKey() {

    try {
        schema = String.valueOf(VaadinSession.getCurrent().getAttribute("X-TenantID"));
        TenantContext.setCurrentTenant(schema);
    } catch (NullPointerException i) {
        schema = null;
        TenantContext.setCurrentTenant("");
    }
    return TenantContext.getCurrentTenant();
}

}`

  1. DataSource configuration as per tutorial, which includes these lines:

AbstractRoutingDataSource dataSource = new DynamicRouter() dataSource.setDefaultTargetDataSource(defaultDataSource()); dataSource.setTargetDataSources(resolvedDataSources);

  1. The Filter class is not required.

All works OK, but not sure if there are any security concerns with this approach?

Storing such an important key in the public available and changeable header send to the client is way more dangerous than storing it in the server-only VaadinSession.

Thanks, yes I thought that may be too risky. In fact I just realized that the http header is unnecessary and can all be done with VaadinSession alone as you say. So if you remove all the all TenantContext code and the Spring https header, it still works.

@Override protected String determineCurrentLookupKey() { try { schema = String.valueOf(VaadinSession.getCurrent().getAttribute("TenantID")); } catch (NullPointerException i) { schema = null; } return schema; }