将动态数据源路由与spring-data-rest相结合

我正在使用动态数据源路由,如本博客文章中所示: http : //spring.io/blog/2007/01/23/dynamic-datasource-routing/

这很好用,但是当我将它与spring-data-rest并浏览我生成的存储库时,我(正确地)得到了一个exception,我的查找键没有定义(我没有设置默认值)。

在与数据库建立任何连接之前,如何以及在何处可以挂钩Spring数据rest请求处理以基于’x’(用户授权,路径前缀或其他)设置lookup-key?

代码方面我的数据源配置主要匹配顶部的博客post,一些基本的实体类,生成的存储库和Spring Boot将所有内容组合在一起。 如果需要我可以发布一些代码,但没有什么可以看到的。

我的第一个想法是利用Spring Security的authentication对象根据附加到身份validation的authorities设置当前数据源。 当然,您也可以将查找键放在自定义UserDetails对象中,甚至也可以放在自定义Authentication对象中。 为了简洁起见,我将专注于基于权威的解决方案。 此解决方案需要有效的身份validation对象(匿名用户也可以拥有有效的身份validation)。 根据您的Spring Security配置,可以按请求或会话完成更改权限/数据源。

我的第二个想法是使用javax.servlet.Filter在Spring Data Rest启动之前在线程局部变量中设置查找键。这个解决方案是独立于框架的,可以在每个请求或会话的基础上使用。

使用Spring Security进行数据源路由

使用SecurityContextHolder访问当前身份validation的权限。 根据当局决定使用哪个数据源。 就像你的代码我没有在我的AbstractRoutingDataSource上设置defaultTargetDataSource。

 public class CustomRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { Set authorities = getAuthoritiesOfCurrentUser(); if(authorities.contains("ROLE_TENANT1")) { return "TENANT1"; } return "TENANT2"; } private Set getAuthoritiesOfCurrentUser() { if(SecurityContextHolder.getContext().getAuthentication() == null) { return Collections.emptySet(); } Collection authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities(); return AuthorityUtils.authorityListToSet(authorities); } } 

在您的代码中,您必须使用满足您需求的UserDetailsS​​ervice替换内存UserDetailsService (inMemoryAuthentication)。 它向您显示有两个不同的用户使用不同的角色TENANT1TENANT2用于数据源路由。

 @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进行数据源路由

创建一个新的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 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(); } } 

正如你的代码我没有在我的AbstractRoutingDataSource上设置defaultTargetDataSource。

 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