代码之家  ›  专栏  ›  技术社区  ›  Tim

将动态数据源路由与spring数据休息相结合

  •  2
  • Tim  · 技术社区  · 10 年前

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

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

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

    从代码上看,我的数据源配置主要与顶部的blogpost相匹配,其中包含一些基本的实体类、生成的存储库和Spring Boot,以将所有内容打包在一起。如果需要,我可以发布一些代码,但没有什么可看的。

    1 回复  |  直到 10 年前
        1
  •  9
  •   ksokol    10 年前

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

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

    使用Spring Security的数据源路由

    使用 SecurityContextHolder 访问当前身份验证的权限。根据当局决定使用哪个数据源。 就像你的代码一样,我没有在我的 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 (在MemoryAuthentication中)使用满足您需求的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(); }
    }
    

    正如您的代码一样,我没有在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