Объедините динамическую маршрутизацию источника данных с Spring-Data-Rest
-
02-01-2020 - |
Вопрос
Я использую динамическую маршрутизацию источника данных, как указано в этом сообщении блога: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