Question

J'utilise le routage dynamique des sources de données comme indiqué dans ce billet de blog :http://spring.io/blog/2007/01/23/dynamic-datasource-routing/

Cela fonctionne bien, mais quand je le combine avec spring-data-rest et en parcourant mes référentiels générés, j'obtiens (à juste titre) une exception indiquant que ma clé de recherche n'est pas définie (je ne définis pas de valeur par défaut).

Comment et où puis-je me connecter à la gestion des demandes de repos de données Spring pour définir la clé de recherche basée sur « x » (autorisations utilisateur, préfixe de chemin ou autre), avant qu'une connexion ne soit établie à la base de données ?

Au niveau du code, la configuration de ma source de données correspond principalement à l'article de blog en haut, avec quelques classes d'entités de base, des référentiels générés et Spring Boot pour tout regrouper.Si besoin, je pourrais poster du code, mais il n'y a pas grand chose à voir là-bas.

Était-ce utile?

La solution

Ma première idée est de tirer parti des fonctionnalités de Spring Security authentication objet pour définir la source de données actuelle en fonction de authorities attaché à l’authentification.Bien sûr, vous pouvez placer la clé de recherche dans un fichier personnalisé UserDetails ou même un objet d'authentification personnalisé.Par souci de brièveté, je me concentrerai sur une solution basée sur les autorités.Cette solution nécessite un objet d'authentification valide (un utilisateur anonyme peut également disposer d'une authentification valide).En fonction de votre configuration Spring Security, la modification de l'autorité/source de données peut être effectuée par demande ou par session.

Ma deuxième idée est de travailler avec un javax.servlet.Filter pour définir la clé de recherche dans une variable locale de thread avant le lancement de Spring Data Rest.Cette solution est indépendante du framework et peut être utilisée par requête ou par session.

Routage de source de données avec Spring Security

Utiliser SecurityContextHolder pour accéder aux autorités d'authentification actuelles.En fonction des autorités, décidez quelle source de données utiliser.Tout comme votre code, je ne définis pas de defaultTargetDataSource sur mon 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);
    }
}

Dans votre code vous devez remplacer le en mémoire UserDetailsService (inMemoryAuthentication) avec un UserDetailsService qui répond à vos besoins.Cela vous montre qu'il existe deux utilisateurs différents avec des rôles différents TENANT1 et TENANT2 utilisé pour le routage de la source de données.

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

Voici un exemple complet : https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-spring-security/spring-data

Routage de source de données avec javax.servlet.Filter

Créez une nouvelle classe de filtre et ajoutez-la à votre web.xml ou enregistrez-le auprès du AbstractAnnotationConfigDispatcherServletInitializer, respectivement.

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

La classe de locataire est un simple wrapper autour d'un fichier statique 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(); }
}

Tout comme votre code, je ne définis pas de defaultTargetDataSource sur mon AbstractRoutingDataSource.

public class CustomRoutingDataSource extends AbstractRoutingDataSource {

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

Vous pouvez désormais changer de source de données avec http://localhost:8080/sandbox/myEntities;tenant=tenant1.Attention, le locataire doit être défini à chaque demande.Alternativement, vous pouvez stocker le locataire dans le HttpSession pour les demandes ultérieures.

Voici un exemple complet : https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-url/spring-data

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top