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

Tim*_*Tim 1 java spring-data spring-data-jpa spring-data-rest

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

这很好,但是当我将它与spring-data-rest浏览我生成的存储库结合起来时,我(正确地)得到了一个异常,我的查找键没有被定义(我没有设置默认值).

在与数据库建立任何连接之前,如何以及在何处可以挂钩Spring数据休息请求处理以基于"x"(用户授权,路径前缀或其他)设置查找键?

代码方面我的数据源配置主要与顶部的blogpost匹配,包括一些基本的实体类,生成的存储库和Spring Boot以将所有内容包装在一起.如果需要我可以发布一些代码,但没有什么可看的.

Kam*_*kol 7

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

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

使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的代码中,您必须使用UserDetailsService满足您需求的UserDetailsS​​ervice 替换内存(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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个完整的例子:https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-spring-security/spring-data

使用javax.servlet.Filter进行数据源路由

创建一个新的过滤器类并将其添加到您的web.xmlAbstractAnnotationConfigDispatcherServletInitializer分别使用.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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(); }
}
Run Code Online (Sandbox Code Playgroud)

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

public class CustomRoutingDataSource extends AbstractRoutingDataSource {

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

现在您可以使用切换数据源http://localhost:8080/sandbox/myEntities;tenant=tenant1.请注意,每个请求都必须设置租户.或者,您可以将租户存储在HttpSession后续请求中.

这是一个完整的例子:https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-url/spring-data