Многопользовательское приложение позволяет одному пользователю получать доступ ко многим учетным записям арендаторов?

Я пишу многопользовательское приложение, используя одну базу данных для каждой модели клиента. Я разрешил каждой учетной записи пользователя доступ к нескольким арендаторам (при условии, что этот арендатор предоставил им доступ)

Каждая страница, отправленная в браузер, включает текущий TenantId в Site.Master.

<%= Html.Hidden("TenantId") %>

Но когда из браузера делается какой-либо запрос (кнопка отправки, AJAX GET или AJAX POST), этот TenantId на самом деле НЕ проверяется, чтобы увидеть, соответствует ли он текущему TenantId пользователя.

Теперь, если пользователь открывает одну вкладку с TenantId = 1, затем подключается к другому арендатору на другой вкладке с TenantId = 2, затем переключается обратно на первую вкладку и получает доступ к данным от арендатора 2.

Что я могу сделать, чтобы исправить это? У меня есть большое количество существующих методов ActionResult и JsonResult, и я не хочу перебирать каждый из них и добавлять

if (request.TenantId != user.CurrentTenantId) return false

Потому что это было бы большим количеством дублированных усилий

Могу ли я изменить базовый контроллер, чтобы он всегда считывал значение TenantId? Это может работать для отправленных запросов (ActionResult), но как насчет запросов AJAX?

Как я могу проверить TenantId страницы внутри действий JsonResult без изменения каждого существующего метода AJAX (их много)?


person JK.    schedule 15.04.2013    source источник


Ответы (4)


Вы можете проверить событие Application_Request в файле Global.asax.cs. Если то, что вам нужно, заполняется через привязку модели MVC, то, возможно, напишите собственный ActionFilter, чтобы проверить его и зарегистрировать его со всеми действиями через GlobalFilter.

person Nick Albrecht    schedule 15.04.2013

Если я правильно понял, пользователи могли одновременно открывать 2 разные вкладки, каждая с другим арендатором. И на каждой странице должны отображаться данные, относящиеся к каждому арендатору.

Таким образом, это означает, что решение, связанное с файлом cookie или сеансом, необходимо отбросить, поскольку арендатор специфичен для каждой вкладки браузера.

И, читая ваш ответ на предложение Сирила Гупты, я понимаю, что скрытый идентификатор tenantId на каждой странице может не отправляться при каждом запросе AJAX. Конечно, одним из решений может быть изменение вашего приложения и обеспечение того, чтобы это всегда имело место с каждым запросом AJAX. В противном случае это также приведет к отбрасыванию глобального фильтра на основе параметров запроса, поскольку tenantId может не всегда присутствовать.

Я думаю, что лучший вариант — добавить сегмент в URL-адрес, содержащий tenantId. Например, заменив маршрут по умолчанию чем-то вроде следующего маршрута (если у вас много разных маршрутов, вам нужно быть очень осторожным, чтобы избежать столкновения маршрутов):

routes.MapRoute(
            name: "Default",
            url: "{tenant}/{controller}/{action}/{id}",
            defaults: new { tenant = "defaultTenant", controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

Таким образом, вы можете быть уверены, что арендатор всегда будет отправляться по каждому запросу, а также у вас может быть 2 разные вкладки с разными арендаторами, отображающими соответствующие данные.

Существуют различные варианты восстановления значения сегмента маршрута.

Привязка автоматически заполнит значение любого параметра с именем «арендатор» в вашем методе действия или любого параметра с именем «арендатор» в классе модели, который является параметром метода действия: public ActionResult Foo (модель FooModel, строковый арендатор) { //и tenant, и model.tenant будут содержать значение сегмента URL return View(); }

Вы также можете написать фильтр, который обращается к значению параметра маршрута (RouteData — это свойство класса ActionExecutingContext и ActionExecutedContext, полученное в качестве параметров методов фильтра), и выполняет некоторую логику. Затем фильтр будет установлен как глобальный фильтр в вашем приложении или на базовом контроллере:

public class FooFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var tenant = filterContext.RouteData.Values["tenant"]
        //do whatever you need to do before executing the action, based on the tenant 
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var tenant = filterContext.RouteData.Values["tenant"]
        //do whatever you need to do after executing the action, based on the tenant
    }
}

Последний вариант — получить прямой доступ к параметру RouteData в вашем базовом классе контроллера. (Поскольку RouteData является свойством базового класса MVC Controller)

Пока вы используете помощники Html и Ajax для создания URL-адресов, клиентский сегмент URL-адреса будет поддерживаться в ваших ссылках. Однако, если у вас есть код jquery, напрямую отправляющий вызовы AJAX с жестко запрограммированными URL-адресами, вам потребуется обновить этот код, чтобы учитывать новый сегмент URL-адреса.

Наконец, если значения tenantId не очень удобны для пользователя, например, целое число, можно задать уникальные имена для каждого арендатора и использовать имена в URL-адресе. Затем вы должны добавить некоторую логику, которая сопоставляет ее с целочисленным значением, необходимым вашему приложению.

person Daniel J.G.    schedule 23.04.2013
comment
Извините, чтобы уточнить: мне не нужно, чтобы обе вкладки работали одновременно. Мне просто нужно, чтобы первая вкладка отказывалась загружать какие-либо дополнительные данные, потому что пользователь теперь вошел в учетную запись второго арендатора. Также я не хочу иметь идентификатор арендатора в URL-адресе, но он доступен в коллекции форм страницы, если это поможет. - person JK.; 24.04.2013
comment
@JK, тогда вы можете использовать аналогичный подход, но восстановить tenantId из запроса (что в основном уже предлагали другие). Проблема в том, что вам нужно убедиться, что tenantId отправляется в каждом запросе AJAX. - person Daniel J.G.; 24.04.2013

Вы можете написать свой собственный фильтр:

Как сделать Я получаю определенный код для выполнения перед каждым действием контроллера в ASP.NET MVC 2? Выполнение кода перед любым действием

Конечно, нет готового ответа на ваш вопрос. Вам нужно написать собственную логику, как обращаться с tenantId. Например, для каждого действия проверьте, не совпадает ли оно с текущим идентификатором клиента сеанса, выполняющим перенаправление. Или поместите его в файл cookie и каждый раз проверяйте в фильтре, равны ли идентификаторы. Тебе решать. С моей точки зрения, cookie предпочтительнее. Но это съедает трафик.

person Alexandr    schedule 18.04.2013

Вы можете применить фильтр на уровне контроллера и проверить отправляемый tenantid. Фильтры уровня контроллера не должны быть более сложными, чем фильтры действий. Для моего проекта мне нужно было проверить авторизацию аналогичным образом, но я переопределил класс контроллера, а затем унаследовал его от своего собственного класса контроллера, поскольку у меня были особые потребности.

Где вы собираетесь хранить идентификатор арендатора на стороне клиента? Мне кажется, что для этого вы должны использовать объект сеанса.

person Cyril Gupta    schedule 16.04.2013
comment
У меня уже есть базовый класс контроллера, от которого наследуются все контроллеры. Как я могу использовать это, чтобы добавить фильтр? И, конечно же, это повлияет только на методы ActionResult (они из отправки формы, поэтому я могу проверить любое из полей формы), но не на методы JsonResult (потому что это отправка AJAX, и они содержат только фактически отправленные поля, а не весь объект коллекции формы ) - person JK.; 17.04.2013