動的データソースルーティングと spring-data-rest を組み合わせる
-
02-01-2020 - |
質問
このブログ投稿で示されているように、私は動的データソース ルーティングを使用しています。http://spring.io/blog/2007/01/23/dynamic-datasource-routing/
これはうまく機能しますが、それを組み合わせると、 spring-data-rest
生成されたリポジトリを参照すると、ルックアップキーが定義されていない(デフォルトを設定していない)という例外が(当然のことながら)発生します。
データベースへの接続が確立される前に、どこで Spring データ レスト リクエスト処理にフックして、「x」 (ユーザー認証、パス プレフィックスなど) に基づいてルックアップ キーを設定できますか?
コード的には、私のデータソース構成は上部のブログ投稿とほとんど一致しており、いくつかの基本的なエンティティクラス、生成されたリポジトリ、およびすべてをまとめるための Spring Boot が含まれています。必要に応じてコードを投稿することもできますが、そこには見るべきものは何もありません。
解決
私の最初のアイデアは、Spring Security を活用することです。 authentication
現在のデータソースに基づいて設定するオブジェクト authorities
認証に付属します。もちろん、カスタムキーにルックアップキーを入れることもできます。 UserDetails
オブジェクト、またはカスタムの Authentication オブジェクトも使用できます。簡潔にするために、当局に基づいた解決策に焦点を当てます。このソリューションには有効な認証オブジェクトが必要です (匿名ユーザーも有効な認証を持つことができます)。Spring Security の構成に応じて、権限/データソースの変更はリクエストごとまたはセッションごとに実行できます。
私の 2 番目のアイデアは、 javax.servlet.Filter
Spring Data Rest が起動する前に、スレッド ローカル変数にルックアップ キーを設定します。このソリューションはフレームワークに依存せず、リクエストごとまたはセッションごとに使用できます。
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);
}
}
コード内でメモリ内の を置き換える必要があります UserDetailsService
(inMemoryAuthentication) のニーズを満たす UserDetailsService を使用します。異なる役割を持つ 2 人の異なるユーザーがいることを示しています。 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;
}
}
テナント クラスは静的クラスの単純なラッパーです。 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