اجمع بين توجيه مصدر البيانات الديناميكي واستراحة بيانات الربيع

StackOverflow https://stackoverflow.com//questions/25083710

سؤال

أنا أستخدم التوجيه الديناميكي لمصدر البيانات كما هو موضح في مشاركة المدونة هذه: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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top