Pergunta

Estou usando o roteamento dinâmico de fonte de dados conforme indicado nesta postagem do blog:http://spring.io/blog/2007/01/23/dynamic-datasource-routing/

Isso funciona bem, mas quando eu combino com spring-data-rest e navegando em meus repositórios gerados, recebo (com razão) uma exceção de que minha chave de pesquisa não está definida (não defino um padrão).

Como e onde posso conectar-me ao tratamento de solicitação de descanso de dados do Spring para definir a chave de pesquisa com base em 'x' (autorizações de usuário, prefixo de caminho ou outro), antes de qualquer conexão ser feita com o banco de dados?

Em termos de código, minha configuração de fonte de dados corresponde principalmente à postagem do blog na parte superior, com algumas classes de entidade básicas, repositórios gerados e Spring Boot para agrupar tudo.Se necessário eu poderia postar algum código, mas não há muito para ver lá.

Foi útil?

Solução

Minha primeira ideia é aproveitar o Spring Security authentication objeto para definir a fonte de dados atual com base em authorities anexado à autenticação.Claro, você pode colocar a chave de pesquisa em um formato personalizado UserDetails objeto ou até mesmo um objeto de autenticação personalizado também.Por uma questão de brevidade, concentrar-me-ei numa solução baseada nas autoridades.Esta solução requer um objeto de autenticação válido (o usuário anônimo também pode ter uma autenticação válida).Dependendo da configuração do Spring Security, a alteração da autoridade/fonte de dados pode ser realizada por solicitação ou sessão.

Minha segunda idéia é trabalhar com um javax.servlet.Filter para definir a chave de pesquisa em uma variável local de thread antes que o Spring Data Rest entre em ação.Esta solução é independente da estrutura e pode ser usada por solicitação ou sessão.

Roteamento de fonte de dados com Spring Security

Usar SecurityContextHolder para acessar as autoridades de autenticação atuais.Com base nas autoridades, decida qual fonte de dados usar.Assim como seu código, não estou definindo um defaultTargetDataSource no meu 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);
    }
}

No seu código você deve substituir o na memória UserDetailsService (inMemoryAuthentication) com um UserDetailsService que atende às suas necessidades.Mostra que existem dois usuários diferentes com funções diferentes TENANT1 e TENANT2 usado para o roteamento da fonte de dados.

@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();
    }
}

Aqui está um exemplo completo: https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-spring-security/spring-data

Roteamento de fonte de dados com javax.servlet.Filter

Crie uma nova classe de filtro e adicione-a ao seu web.xml ou registre-o no AbstractAnnotationConfigDispatcherServletInitializer, respectivamente.

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;
    }
}

A classe de locatário é um wrapper simples em torno de um objeto estático 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(); }
}

Assim como seu código, não estou definindo um defaultTargetDataSource em meu AbstractRoutingDataSource.

public class CustomRoutingDataSource extends AbstractRoutingDataSource {

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

Agora você pode alternar a fonte de dados com http://localhost:8080/sandbox/myEntities;tenant=tenant1.Cuidado, pois o locatário deve ser definido em todas as solicitações.Alternativamente, você pode armazenar o inquilino no HttpSession para solicitações subsequentes.

Aqui está um exemplo completo: https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-url/spring-data

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top