Объедините динамическую маршрутизацию источника данных с пружиной data-rest

1

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

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

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

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

  • 0
    я мог бы пропустить ваш вопрос ... разве CustomerRoutingDataSource в примере не выполняет ту работу, которую вы хотите?
  • 0
    Не тогда, когда вы хотите объединить его с сгенерированным API отдыха из данных весеннего отдыха
Показать ещё 3 комментария
Теги:
spring-data-jpa
spring-data-rest
spring-data

1 ответ

7
Лучший ответ

Моя первая идея - использовать объект authentication Spring Security для установки текущего источника данных на основе 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;
    }
}

Класс арендатора - простая оболочка вокруг статического 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

  • 0
    Большое спасибо за подробный ответ. Ваш подход действительно выглядит как что-то, что может сработать, но, к сожалению, я не смог реализовать его из-за проблем в другой части моего кода на этой неделе. Я надеюсь, что скоро вернусь к нему и отмечу ваше решение как ответ, как только я смогу подтвердить, что оно работает. Еще раз спасибо!
  • 0
    Извините, я только дошел до этого сейчас, но это работает как шарм, большое спасибо! Я придерживался подхода, основанного на фильтрах, поскольку арендаторы и базы данных находятся во многих отношениях.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню