将动态数据源路由与spring-data-rest相结合 [英] Combine Dynamic datasource routing with spring-data-rest

查看:171
本文介绍了将动态数据源路由与spring-data-rest相结合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm using Dynamic datasource routing as indicated in this blog post: http://spring.io/blog/2007/01/23/dynamic-datasource-routing/

这很好,但是,当我将它与 spring-data-rest 组合并浏览我生成的存储库时,我(正确地)得到一个异常,我的查找键未定义(我没有设置默认情况下。

This works fine, but when I combine it with spring-data-rest and browsing of my generated repositories I (rightfully) get an exception that my lookup-key is not defined (I do not set a default).

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

How and where can I hook into the Spring data rest request handling to set the lookup-key based on 'x' (user authorizations, path prefix, or other), before any connection is made to the database?

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

Code-wise my datasource configuration just mostly matches the blogpost at the top, with some basic entity classes, generated repositories and Spring Boot to wrap everything together. If need I could post some code, but there's nothing much to see there.

推荐答案

我的第一个想法是利用Spring Security的< a href =http://docs.spring.io/autorepo/docs/spring-security/3.2.5.RELEASE/apidocs/org/springframework/security/core/Authentication.html\"rel =noreferrer> 身份验证 对象,用于根据身份验证附带的权限设置当前数据源。
当然,您可以将查找键放在自定义 UserDetails 对象,甚至是自定义身份验证对象。为了简洁起见,我将专注于基于权威的解决方案。
此解决方案需要有效的身份验证对象(匿名用户也可以拥有有效的身份验证)。根据您的Spring Security配置,可以按请求或会话完成更改权限/数据源。

My first idea is to leverage Spring Security's authentication object to set current datasource based on authorities attached to the authentication. Of course, you can put the lookup key in a custom UserDetails object or even a custom Authentication object, too. For sake of brevity I`ll concentrate on a solution based on authorities. This solution requires a valid authentication object (anonymous user can have a valid authentication, too). Depending on your Spring Security configuration changing authority/datasource can be accomplished on a per request or session basis.

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

My second idea is to work with a javax.servlet.Filter to set lookup key in a thread local variable before Spring Data Rest kicks in. This solution is framework independent and can be used on a per request or session basis.

使用Spring Security的数据源路由

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

Use SecurityContextHolder to access current authentication's authorities. Based on the authorities decide which datasource to use. Just as your code I'm not setting a defaultTargetDataSource on my 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);
    }
}

在你的代码中你必须更换内存 UserDetailsS​​ervice (inMemoryAuthentication),其中包含满足您需求的UserDetailsS​​ervice。
它向您显示有两个不同的用户具有不同的角色 TENANT1 TENANT2 用于数据源路由。

In your code you must replace the in memory UserDetailsService (inMemoryAuthentication) with a UserDetailsService that serves your need. It shows you that there are two different users with different roles TENANT1 and TENANT2 used for the datasource routing.

@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

Here is a complete example: https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-spring-security/spring-data

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

创建一个新的过滤器类并将其添加到 web.xml 或使用 AbstractAnnotationConfigDispatcherServletInitializer

Create a new filter class and add it to your web.xml or register it with the AbstractAnnotationConfigDispatcherServletInitializer, respectively.

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

租户类是一个围绕静态 ThreadLocal

Tenant class is a simple wrapper around a static 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一样。

Just as your code I`m not setting a defaultTargetDataSource on my AbstractRoutingDataSource.

public class CustomRoutingDataSource extends AbstractRoutingDataSource {

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

现在你可以用 HTTP://本地主机:8080 /沙箱/ myEntities;租户= tenant1 。请注意,每个请求都必须设置租户。或者,您可以将租户存储在 HttpSession 中以用于后续请求。

Now you can switch datasource with http://localhost:8080/sandbox/myEntities;tenant=tenant1. Beware that tenant has to be set on every request. Alternatively, you can store the tenant in the HttpSession for subsequent requests.

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

Here is a complete example: https://github.com/ksokol/spring-sandbox/tree/sdr-routing-datasource-url/spring-data

这篇关于将动态数据源路由与spring-data-rest相结合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆