Объедините динамическую маршрутизацию источника данных с Spring-Data-Rest

StackOverflow https://stackoverflow.com//questions/25083710

Вопрос

Я использую динамическую маршрутизацию источника данных, как указано в этом сообщении блога:http://spring.io/blog/2007/01/23/dynamic-datasource-routing/

Это работает нормально, но когда я объединяю его с spring-data-rest и просматривая мои сгенерированные репозитории, я (по праву) получаю исключение, что мой ключ поиска не определен (я не устанавливаю значение по умолчанию).

Как и где я могу подключиться к обработке запроса на сохранение данных Spring, чтобы установить ключ поиска на основе «x» (авторизация пользователя, префикс пути или другое), прежде чем будет установлено какое-либо соединение с базой данных?

С точки зрения кода моя конфигурация источника данных в основном соответствует сообщению в блоге вверху, с некоторыми базовыми классами сущностей, сгенерированными репозиториями и Spring Boot, чтобы объединить все воедино.Если нужно, я мог бы опубликовать код, но там особо не на что смотреть.

Это было полезно?

Решение

Моя первая идея — использовать Spring Security. authentication объект для установки текущего источника данных на основе authorities прилагается к аутентификации.Конечно, вы можете поместить ключ поиска в собственный UserDetails объект или даже собственный объект аутентификации.Для краткости я сосредоточусь на решении, основанном на авторитетных источниках.Для этого решения требуется действительный объект аутентификации (анонимный пользователь также может иметь действительную аутентификацию).В зависимости от вашей конфигурации Spring Security изменение полномочий/источника данных может выполняться для каждого запроса или сеанса.

Моя вторая идея — работать с javax.servlet.Filter чтобы установить ключ поиска в локальной переменной потока до того, как заработает Spring Data Rest.Это решение не зависит от платформы и может использоваться для каждого запроса или сеанса.

Маршрутизация источника данных с помощью Spring Security

Использовать SecurityContextHolder для доступа к текущим полномочиям аутентификации.На основании этого власти решают, какой источник данных использовать.Как и ваш код, я не устанавливаю defaultTargetDataSource на свой AbstractRoutingDataSource.

public class CustomRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        Set<String> authorities = getAuthoritiesOfCurrentUser();
        if(authorities.contains("ROLE_TENANT1")) {
            return "TENANT1";
        }
        return "TENANT2";
    }

    private Set<String> getAuthoritiesOfCurrentUser() {
        if(SecurityContextHolder.getContext().getAuthentication() == null) {
            return Collections.emptySet();
        }
        Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        return AuthorityUtils.authorityListToSet(authorities);
    }
}

В вашем коде вы должны заменить в памяти UserDetailsService (inMemoryAuthentication) с помощью UserDetailsService, который соответствует вашим потребностям.Он показывает, что есть два разных пользователя с разными ролями. TENANT1 и TENANT2 используется для маршрутизации источника данных.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
            .withUser("user1").password("user1").roles("USER", "TENANT1")
            .and()
            .withUser("user2").password("user2").roles("USER", "TENANT2");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**")
            .authorizeRequests()
            .antMatchers("/**").hasRole("USER")
            .and()
            .httpBasic()
            .and().csrf().disable();
    }
}

Вот полный пример: https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-spring-security/spring-data

Маршрутизация источника данных с помощью javax.servlet.Filter

Создайте новый класс фильтра и добавьте его в свой web.xml или зарегистрируйте его в AbstractAnnotationConfigDispatcherServletInitializer, соответственно.

public class TenantFilter implements Filter {

    private final Pattern pattern = Pattern.compile(";\\s*tenant\\s*=\\s*(\\w+)");

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String tenant = matchTenantSystemIDToken(httpRequest.getRequestURI());
        Tenant.setCurrentTenant(tenant);
        try {
            chain.doFilter(request, response);
        } finally {
            Tenant.clearCurrentTenant();
        }
    }

    private String matchTenantSystemIDToken(final String uri) {
        final Matcher matcher = pattern.matcher(uri);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }
}

Класс Tenant — это простая оболочка статического ThreadLocal.

public class Tenant {

    private static final ThreadLocal<String> TENANT = new ThreadLocal<>();

    public static void setCurrentTenant(String tenant) { TENANT.set(tenant); }

    public static String getCurrentTenant() { return TENANT.get(); }

    public static void clearCurrentTenant() { TENANT.remove(); }
}

Как и ваш код, я не устанавливаю defaultTargetDataSource в своем AbstractRoutingDataSource.

public class CustomRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        if(Tenant.getCurrentTenant() == null) {
            return "TENANT1";
        }
        return Tenant.getCurrentTenant().toUpperCase();
    }
}

Теперь вы можете переключить источник данных с помощью http://localhost:8080/sandbox/myEntities;tenant=tenant1.Помните, что арендатор должен быть установлен при каждом запросе.Альтернативно, вы можете сохранить арендатора в HttpSession для последующих запросов.

Вот полный пример: https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-url/spring-data

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top