اجمع بين توجيه مصدر البيانات الديناميكي واستراحة بيانات الربيع
-
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
كائن أو حتى كائن مصادقة مخصص أيضًا.من أجل الإيجاز، سأركز على حل يعتمد على السلطات.يتطلب هذا الحل كائن مصادقة صالحًا (يمكن أن يكون لدى المستخدم المجهول مصادقة صالحة أيضًا).اعتمادًا على تكوين Spring Security الخاص بك، يمكن تغيير السلطة/مصدر البيانات على أساس كل طلب أو جلسة.
فكرتي الثانية هي العمل مع أ 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 الذي يخدم احتياجاتك.يوضح لك أن هناك مستخدمين مختلفين لهما أدوار مختلفة 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(); }
}
تمامًا كما هو الحال مع الكود الخاص بك، لا أقوم بتعيين defaultTargetDataSource على AbstractRoutingDataSource.
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