Я использую Dynamic datasource routing, как указано в этом сообщении в блоге: http://spring.io/blog/2007/01/23/dynamic-datasource-routing/
Это прекрасно работает, но когда я совмещаю это с spring-data-rest
Spring spring-data-rest
и просматриваю мои сгенерированные репозитории, я (по праву) получаю исключение, которое мой ключ поиска не определен (я не устанавливаю значение по умолчанию).
Как и где я могу подключиться к обработке запроса данных Spring для установки ключа поиска на основе "x" (авторизации пользователя, префикса пути или другого), прежде чем какое-либо соединение будет сделано в базе данных?
В кодовом варианте моя конфигурация источника данных в основном соответствует блогу в верхней части, с некоторыми основными классами сущностей, сгенерированными репозиториями и Spring Boot, чтобы обернуть все вместе. Если понадобится, я могу опубликовать код, но там ничего не видно.
Моя первая идея - использовать объект 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