Реализуйте мультиарендность в среде без сохранения состояния с помощью Spring, Hibernate и Tomcat.

Я пытаюсь понять, как мы создаем RESTful API для продукта SaaS, где он предполагает мультитенантность. Стек технологий - это Java, использующая Spring и Hibernate и развертывающая WAR на Tomcat.

Моя основная проблема заключается в том, как поддерживать tenant_id в вызове REST, чтобы приложение могло использовать правильное соединение с базой данных при выполнении CRUD. Видя, что Tomcat использует пул потоков и повторно использует потоки, мы не должны использовать ThreadLocal.

Я читал, что slf4j поддерживает это с реализацией MDC для ведения журнала. Фильтр сервлета поддерживает tenant_id заранее и очищает его при выходе из фильтра. Следовательно, регистратор использует в сообщениях правильный tenant_id.

В то же время использование ThreadLocal противоречит принципу отсутствия состояния, поскольку он неявно добавляет состояние.

Кроме того, идея создания какого-то объекта ContextSession, содержащего tenant_id, и его передачи, похоже, не решает мою проблему. Поскольку этот объект будет передан по уровням DAL и DAO для загрузки объектов. Я хочу избежать этой сильной связи в этом классе ContextSession, а также необходимости включать ее во многие сигнатуры методов.

Как реализовать мультиарендность в среде без сохранения состояния?


person P_C    schedule 09.03.2017    source источник
comment
Вы решили проблему (без использования HttpSession?) У меня такая же проблема, когда мне нужно выбрать базу данных на основе пользователя.   -  person Yadu Krishnan    schedule 16.04.2018
comment
Я использовал ThreadLocal с используемым потоком Tomcat. В моем случае URL-адрес (в конечном итоге заголовки) содержит идентификатор. Поскольку каждый пользовательский запрос будет использовать уникальный поток, ThreadLocal будет хранить этот идентификатор. Если вы не используете Tomcat, вам нужен способ извлечь идентификатор и убедиться, что у вас есть 1 поток на вызов.   -  person P_C    schedule 17.04.2018


Ответы (2)


Когда пользователь входит в ваше приложение, он каким-то образом связан с клиентом. Таким образом, информация о клиенте должна быть доступна из Spring SecurityContext.

Например, если у меня есть приложение с двумя арендаторами; tenantA и tenantB. При входе в систему пользователь должен каким-то образом указать, к какому арендатору он принадлежит. Это можно сделать различными способами, например, используя разные имена хостов (например, tenantA.myapp.com, tenantB.myapp.com) или параметры URL-адреса, или вводя информацию о клиенте в форму входа в систему. Затем при аутентификации пользователя вам необходимо использовать область аутентификации, связанную с конкретным арендатором. Как часть аутентификации, Spring SecurityContext должен быть настроен так, чтобы он содержал информацию, которая позволяет вам определять, к какому клиенту принадлежит пользователь при последующих вызовах службы от пользователя. SecurityContext должен быть доступен из разных сервисных уровней вашего приложения без необходимости явно программировать что-либо для этого.

person httPants    schedule 09.03.2017
comment
Спасибо. Итак, как мне внедрить SecurityContex для tenant_id в DAO? Я предполагаю, что его объем будет сеансом? - person P_C; 09.03.2017
comment
См. baeldung.com/get-user-in-spring-security для идей о том, как получить доступ к SecurityContext - person httPants; 09.03.2017
comment
Кроме того, SecurityContext не обязательно должен быть привязан к сеансу. Если вы используете что-то вроде oauth2 с сеансами без сохранения состояния, токен, используемый в запросе, будет храниться в контексте безопасности и содержать информацию о сфере (которая сообщит вам арендатора). - person httPants; 09.03.2017

Существует множество способов связать текущий HttpSession с конкретным арендатором.

  1. Укажите какой-либо тип параметра client_id в URL-адресе, который используется для обратного просмотра указанного внутреннего сервера tenant через централизованную общую клиентскую базу данных.

  2. Свяжите аутентифицированного пользователя с конкретным tenant. Когда пользователи проходят проверку подлинности в централизованной базе данных пользователей, поиск клиента выполняется на основе учетной записи пользователя.

Как это tenant_id передается на нижние уровни приложения, на самом деле дело вкуса.

Мой первый вариант - использовать ThreadLocal. Если вы уже используете Spring Security, вы уже используете ThreadLocal переменные и, возможно, даже не подозреваете об этом.

Использование ThreadLocal переменных не нарушает структуру вашего приложения без сохранения состояния. У вас просто есть фрагменты кода, которые ожидают, что переменная будет содержать определенное значение, которое она должна использовать. Это просто причудливый способ передать состояние между уровнями вашего приложения без явной передачи его в качестве аргументов в сигнатуре метода.

Очевидно, что два других варианта - использовать какой-либо объект Context или просто передать значение непосредственно ниже по потоку.

В веб-приложении я обычно делал это с помощью некоторого перехватчика или фильтра, который при каждом запросе просматривал параметр запроса client_id, получал связанный tenantId и устанавливал его в специальной переменной ThreadLocal.

После того, как вызов chain.doFilter( request, response ); вернул или сгенерировал исключение, вы просто очищаете переменную ThreadLocal.

person Naros    schedule 09.03.2017